web-dev-qa-db-ja.com

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

正式にはs[〜#〜] u [〜#〜][〜#〜] q [〜#〜])とします= {[〜#〜] v [〜#〜] | [〜#〜] v [〜#〜][〜#〜] u [〜#〜]および[〜#〜] v [〜 #〜][〜#〜] q [〜#〜]}ここで[〜#〜] u [〜#〜][〜#〜] q [〜#〜]、および[〜#〜] v [〜#〜]すべてがセットを表し、[〜#〜] u [ 〜#〜]より具体的には、セットのセットを表します。例として、[〜#〜] u [〜#〜][〜#を使用してクックブックのさまざまなレシピに必要な成分のセットである場合があります。 〜] q [〜#〜]私が持っている材料のセットを表す[〜#〜] v [〜#〜]それらの材料を使って作成できるレシピを表す。クエリs[〜#〜] u [〜#〜][〜#〜] q [〜#〜])は対応します「これらの成分で何が作れますか?」

私が探しているのは、[〜#〜] u [〜#〜]にインデックスを付けるデータ表現であり、s(- [〜#〜] u [〜#〜][〜#〜] q [〜#〜])ここで[〜#〜] q [〜 #〜]および[〜#〜] u [〜#〜]のすべてのメンバーは、一般的に[〜#〜]のすべてのメンバーの結合に比べて小さくなりますu [〜#〜]。さらに、[〜#〜] u [〜#〜]を効率的に更新できるようにしたい(たとえば、レシピを追加または削除する)。

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

ソリューションについて考える限り、私はセットの決定木を作成することを考えていました[〜#〜] u [〜#〜]。ツリーの各ノードで、「成分リストにxが含まれていますか?」という質問xを選択すると、回答によって排除される[〜#〜] u [〜#〜]のメンバーの数を最大化するように選択されます。 [〜#〜] u [〜#〜]が更新されると、正しい結果を見つけるために必要な質問の数を最小限に抑えるために、この決定木を再調整する必要があります。別の考えは[〜#〜] u [〜#〜]n-dimension boolean 'octree'(where n =は固有の成分の数です)。

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

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

11
nben

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

以下は、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万の材料、DBに1,000のレシピ、レシピあたり50の材料、および100の材料を使用できるようにします。つまりすべての値が、指定した最大のユースケース以上である。

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

4
Nebu Pookins