算法/数据结构来回答“我可以用这套配料制作什么食谱?”


11

形式上,令sUQ)= { V | VùVQ },其中üQ,和V都表示集,和ü,更具体而言,代表一组套。举例来说,U可能是食谱中各种配方所需的一组(一组)配料,其中Q代表一组配料,我有V表示我可以用这些配料制作的配方。查询sUQ)对应于“这些成分我能做什么?”的问题

我正在寻找的是一个数据表示该指标ü以这样一种方式,它支持高效的查询小号üQ),其中Q和全体成员ü一般会比较小到所有成员的工会ü。另外,我希望它能够有效地更新U(例如,添加或删除配方)。

我忍不住想必须很好地理解这个问题,但是我找不到它的名称或参考。有谁知道有效解决此问题的策略,或者我可以在此获得更多了解的地方?

就考虑解决方案而言,我曾想过要为集合U建立决策树。在树的每个节点上,问题“您的成分表中是否包含x?” 将要求选择x来最大化被答案消除的U成员的数量。随着U的更新,将需要重新平衡决策树以最小化找到正确结果所需的问题数量。另一个想法是用一个n维布尔“八叉树”(其中n是唯一成分的数量)表示U。

我相信“用这些成分可以制成哪些食谱?” 可以通过以下方法得到答案:将食谱中的食谱(所需的成分集)的笛卡尔乘积与一个成分具有的幂集相乘,然后对两个元素均相等的对进行过滤以得到有序对,但是这不是一个有效的解决方案,我要问的是如何优化这种操作;如何在SQL中将其组合为有效的?SQL怎么做才能使其高效?

尽管我使用的是食谱和一组配料的食谱,但我预计尽管配料的数量很多,但“食谱”和“配料”的数量将非常大(每种多达数十万)在给定的配方中,给定的配料集中的配料数量将相对较小(典型的“食谱”大约为10-50,典型的“配料”大约为100)。另外,最常见的操作将是查询sUQ),因此它应该是最佳的。这也意味着,需要检查每个配方或对每种成分进行操作的蛮力算法本身会很不理想。通过巧妙的缓存,


1
SQL数据库应该可以轻松解决的问题。
罗伯特·哈维

1
根据您的其他描述,这听起来像是Orbitz规模的问题。Orbitz的搜索引擎使用Lisp引擎,该引擎会筛选十亿个左右的数据点,以获取适合您特定行程的航班列表。它的非功能性要求是必须在10秒或更短的时间内返回解决方案。请参阅此处paulgraham.com/carl.html,但请注意该信息已经很旧了。
罗伯特·哈维

这个问题涉及面很广,分为两个部分:数据结构和算法,用于查找作为配料子集的现有配方,以及如何针对大数据进行缩放。我认为这应该是两个问题。在缩小算法部分之前,您无法真正解决大数据部分。user16054已获得有关如何在关系数据库表示中使用联接表的帮助。如果将此问题缩小到算法/数据结构部分,或者提出了另一个独立的问题,我也许可以提供建议。
2015年

Answers:


4

对于您给出的数字,只需蛮力就可以了。

这是一个JavaScript程序,它针对数据库中的10种成分,数据库中的10种配方对其进行强行强制处理,每个配方需要2种成分,而我有5种可用成分:

var i, j;
var numIngredients = 10;
var numRecipes = 10;
var numIngredientsPerRecipe = 2;
var numIngredientsInQuery = 5;

function containsAll(needles, haystack){ 
  var i, len;
  for(i = 0 , len = needles.length; i < len; i++){
      if(haystack.indexOf(needles[i]) == -1) {
          return false;
      }
  }
  return true;
}

// Set up a fake DB of recipes
var ingredients = [];
for (i = 0; i < numIngredients; i++) {
    ingredients.push(i);
}
console.log('Here are the ingredients:', ingredients);

var recipes = [];
for (i = 0; i < numRecipes; i++) {
    var neededIngredients = [];
    for (j = 0; j < numIngredientsPerRecipe; j++) {
        neededIngredients.push(Math.floor(Math.random() * numRecipes));
    }
    recipes.push({ recipeId: i, needed: neededIngredients});
}
console.log('Here are the recipes:', recipes);

// Set up a fake query
var ingredientsAvailable = [];
for (i = 0; i < numIngredientsInQuery; i++) {
    ingredientsAvailable.push(Math.floor(Math.random() * numRecipes));
}

console.log("Here's a query:", ingredientsAvailable);

//Time how long brute force takes
var start = Date.now();
var result = [];
for (i = 0; i < numRecipes; i++) {
    var candidateRecipe = recipes[i];
    if (containsAll(candidateRecipe.needed, ingredientsAvailable)) {
        result.push(candidateRecipe);
    }
}
var end = Date.now();
console.log('Found ' + result.length + ' recipes in ' + (end - start) + ' milliseconds.');
console.log(result);

它在0毫秒内运行。我选择了这些较小的数字,以便您可以自己运行几次,并说服自己做到了您想要的并且相对没有错误。

现在更改它,以便我们在数据库中有1'000'000个成分,在数据库中有1'000'000个配方,每个配方50个成分以及100种可用的成分。即所有等于或大于您给出的最大用例的值。

它在nodejs下运行的时间为125毫秒,这是最愚蠢的实现,完全无需优化。


1
除非OP的要求发生变化,否则没有理由不采用这种方法。聪明的数据结构?不,够快吗?是。可维护且易于理解?明确地。
J特拉纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.