Algorithmus / Datenstruktur zur Beantwortung von „Welche Rezepte kann ich mit diesen Zutaten machen?“

11

Formal sei s ( U , Q ) = { V | VU und VQ }, wobei U , Q und V alle Mengen darstellen und U , genauer gesagt, eine Menge von Mengen darstellt. Zum Beispiel könnte U ein Satz der (Sätze von) Zutaten sein, die für verschiedene Rezepte in einem Kochbuch erforderlich sind, wobei Q den Satz von Zutaten darstellt, die ich habe. V steht für ein Rezept, das ich mit diesen Zutaten machen könnte. Die Abfrage s ( U , Q.) entspricht der Frage "Was kann ich alles mit diesen Zutaten machen?"

Was ich suche ist eine Darstellung der Daten , dass Indizes U derart , dass es eine effiziente Abfrage unterstützt s ( U , Q ) , wobei Q und alle Mitglieder von U verglichen werden im allgemeinen klein , um die Vereinigung aller Mitglieder von U . Außerdem möchte ich, dass es U effizient aktualisieren kann (z. B. ein Rezept hinzufügen oder entfernen).

Ich kann nicht anders, als zu denken, dass dieses Problem gut verstanden werden muss, aber ich konnte keinen Namen oder eine Referenz dafür finden. Kennt jemand eine Strategie, um dies effizient zu lösen, oder einen Ort, an dem ich mehr darüber lesen kann?

Wenn ich über eine Lösung nachdachte, dachte ich, ich sollte einen Entscheidungsbaum für die Menge U erstellen . An jedem Knoten im Baum wird die Frage "Enthält Ihre Zutatenliste x ?" würde mit x ausgewählt werden, um die Anzahl der Mitglieder von U zu maximieren, die durch die Antwort eliminiert werden. Wenn U aktualisiert wird, muss dieser Entscheidungsbaum neu ausgeglichen werden, um die Anzahl der Fragen zu minimieren, die erforderlich sind, um das richtige Ergebnis zu finden. Ein anderer Gedanke ist, U mit so etwas wie einem n- dimensionalen booleschen 'Octree' darzustellen (wobei n die Anzahl der eindeutigen Bestandteile ist).

Ich glaube, dass "Welche Rezepte können mit diesen Zutaten gemacht werden?" kann beantwortet werden, indem man das kartesische Produkt der (für die) Zutaten erforderlichen Sätze im Kochbuch mit dem Potenzsatz der Zutaten nimmt und die resultierenden geordneten Paare nach Paaren filtert, in denen beide Elemente gleich sind, aber dies ist kein effiziente Lösung, und ich frage, wie diese Art von Betrieb optimiert werden kann; Wie würde man dies in SQL so zusammenstellen, dass es effizient wäre, und was macht SQL, damit dies effizient ist?

Obwohl ich die Abbildung eines Kochbuchs mit Rezepten und einer Reihe von Zutaten verwende, gehe ich davon aus, dass die Anzahl der „Rezepte“ und die Anzahl der „Zutaten“ sehr groß sein wird (jeweils bis zu Hunderttausende), obwohl die Anzahl der Zutaten In einem bestimmten Rezept ist die Anzahl der Zutaten in einem bestimmten Zutaten-Set relativ gering (wahrscheinlich etwa 10-50 für ein typisches "Rezept" und etwa 100 für ein typisches "Zutaten-Set"). Darüber hinaus ist die häufigste Operation die Abfrage s ( U , Q ), daher sollte sie am optimalsten sein. Dies bedeutet auch, dass ein Brute-Force-Algorithmus, bei dem jedes Rezept überprüft oder jede Zutat bearbeitet werden muss, für sich genommen unerwünscht langsam ist. Mit cleverem Caching

nben
quelle
1
Ein Problem, das mit einer SQL-Datenbank leicht lösbar sein sollte.
Robert Harvey
1
Basierend auf Ihrer zusätzlichen Beschreibung klingt dies wie ein Problem im Orbitz-Maßstab. Die Suchmaschine von Orbitz verwendet eine Lisp-Suchmaschine, die etwa eine Milliarde Datenpunkte durchsucht, um eine Liste der Flüge zu erhalten, die für Ihre spezifische Reiseroute geeignet sind. Die nicht funktionierende Anforderung besteht darin, dass eine Lösung innerhalb von 10 Sekunden oder weniger zurückgegeben werden muss. Siehe hier paulgraham.com/carl.html , beachten Sie jedoch, dass die Informationen ziemlich alt sind.
Robert Harvey
Diese Frage ist ziemlich weit gefasst und besteht aus zwei Teilen: einer Datenstruktur und einem Algorithmus zum Auffinden vorhandener Rezepte, die Teilmengen von Zutaten sind, und wie diese für große Datenmengen skaliert werden können. Meiner Meinung nach sollten dies zwei Fragen sein. Sie können den großen Datenteil erst dann wirklich ansprechen, wenn Sie den Algorithmus-Teil eingrenzen. user16054 hat bereits Hilfe erhalten, wie Verknüpfungstabellen in einer relationalen Datenbankdarstellung verwendet werden. Wenn diese Frage auf den Algorithmus- / Datenstrukturteil beschränkt ist oder eine andere unabhängige Frage gestellt wurde, kann ich möglicherweise Vorschläge machen.
Rocky

Antworten:

4

Für die Zahlen, die Sie gegeben haben, erzwingen Sie es einfach brutal.

Hier ist ein JavaScript-Programm, das es für 10 Zutaten in der DB, 10 Rezepte in der DB, jedes Rezept benötigt 2 Zutaten und ich habe 5 Zutaten zur Verfügung:

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);

Es läuft in 0 Millisekunden. Ich habe diese kleinen Zahlen ausgewählt, damit Sie sie ein paar Mal selbst ausführen und sich davon überzeugen können, dass sie das tun, was Sie wollen, und relativ frei von Fehlern sind.

Ändern Sie es jetzt so, dass wir 1'000'000 Zutaten in der DB, 1'000'000 Rezepte in der DB, 50 Zutaten pro Rezept und 100 Zutaten zur Verfügung haben. Dh Werte, die alle gleich oder größer sind als der größte Anwendungsfall, den Sie angegeben haben.

Es läuft in 125 Millisekunden unter nodejs, und dies ist mit der dümmsten Implementierung ohne jeglichen Optimierungsaufwand.

Nebu Pookins
quelle
1
Sofern sich die Anforderungen des OP nicht ändern, gibt es keinen Grund, diesen Ansatz nicht zu wählen. Clevere Datenstruktur? Schnell genug? Ja. Wartbar und leicht zu verstehen? Ganz sicher.
J Trana