「この一連の材料でどのレシピを作成できるか」に答えるためのアルゴリズム/データ構造


11

正式には、sUQ)= { V | VUVQ } UQ、およびVがすべてのセットを表し、Uは、より具体的には、セットの集合を表します。例として、Uはクックブックのさまざまなレシピに必要な材料のセット(セット)であり、Qは材料のセットを表し、Vはそれらの材料で作成できるレシピを表します。クエリsUQ)「これらの成分で何が作れるのか」という質問に対応します

私が探しているインデックスというデータ表現であるU、それはの効率的なクエリをサポートするような方法で、SをUQは)ここで、QとのすべてのメンバーUは、一般的に、すべてのメンバーの組合に比べて小さくなりますU。さらに、Uを効率的に更新できるようにしたい(たとえば、レシピの追加または削除)。

私はこの問題をよく理解する必要があると思わずにはいられませんが、名前やリファレンスを見つけることができませんでした。これを効率的に解決するための戦略、または私がそれについてもっと読むことができる場所を誰かが知っていますか?

解決策について考える限り、私が持っていたのは、集合Uの決定木を構築することでした。ツリーの各ノードで、「成分リストにxが含まれていますか?」という質問 回答によって排除されるUのメンバーの数を最大化するためにxを選択して尋ねられます。Uが更新され、この決定木は、正しい結果を見つけるために必要な質問の数を最小限にするために再バランスする必要があります。もう1つの考えは、n次元のブール「オクトリー」(nは一意の成分の数)のようなものでUを表すことです。

「これらの成分でどんなレシピが作れるの?」クックブック内の(必要な成分のセット)レシピのデカルト積を、ある成分のパワーセットで取得し、両方の要素が等しいペアの結果として順序付けられたペアをフィルタリングすることで応答できますが、これは効率的な解決策、そして私が求めているのは、この種の操作を最適化する方法です。効率的になるようにSQLでこれをどのように構成し、これを効率的にするためにSQLで何ができるのでしょうか。

私はレシピと食材のセットのクックブックのイラストを使用していますが、「レシピ」の数と「食材」の数は非常に多くなると予想します(それぞれ数十万まで)。ただし、食材の数は特定のレシピでは、特定の材料セットの材料の数は比較的少なくなります(通常、「レシピ」の場合は約10-50、一般的な「材料セット」の場合は約100)。さらに、最も一般的な操作はクエリsUQ)であるため、最も最適なはずです。これはまた、すべてのレシピをチェックしたり、すべての材料を操作したりする必要があるブルートフォースアルゴリズムは、それだけでは望ましくないほど遅くなることを意味します。巧妙なキャッシングで、


1
SQLデータベースで簡単に解決できる問題。
ロバートハーベイ

1
追加の説明に基づいて、これはOrbitzスケールの問題のように聞こえます。Orbitzの検索エンジンは、10億ほどのデータポイントをふるいにかけるLispエンジンを使用して、特定の旅程に適したフライトのリストを取得します。非機能要件は、ソリューションを10秒以内に返す必要があることです。ここpaulgraham.com/carl.htmlを参照してください。ただし、情報がかなり古いことに注意してください。
ロバートハーヴェイ

この質問はかなり広範であり、2つの部分があります。1つは材料のサブセットである既存のレシピを見つけるためのデータ構造とアルゴリズムであり、これを大規模データ用にスケーリングする方法です。私の見解では、これは2つの質問であるべきです。アルゴリズムの部分を絞り込むまでは、大きなデータ部分を実際に処理することはできません。user16054は、リレーショナルデータベース表現での結合テーブルの使用方法に関するヘルプをすでに入手しています。この質問がアルゴリズム/データ構造の部分に絞り込まれた場合、または別の独立した質問が行われた場合、私は提案を提供できるかもしれません。
岩が多い

回答:


4

あなたが与えた数については、それを総当たりしてください。

これは、DBの10成分、DBの10のレシピに対してブルートフォースを実行するJavaScriptプログラムです。各レシピには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ミリ秒で実行されます。私はこれらの小さい数を選択したので、自分で数回実行して、望んだとおりに機能し、バグが比較的少ないことを納得させることができます。

それを変更して、DBに1,000,000の成分、DBに1,000,000のレシピ、レシピあたり50の成分、および100の成分を使用できるようにします。つまり、すべて最大のユースケース以上の値です。

これはnodejsの下で125ミリ秒で実行されます。これは、最適化するための努力がまったくない、最も馬鹿げた実装によるものです。


1
OPの要件が変更されない限り、このようなアプローチを取らない理由はありません。巧妙なデータ構造?いいえ。十分速いですか?はい。メンテナンス可能で理解しやすい?確実に。
J Trana、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.