Algorithme / structure de données pour répondre «quelles recettes puis-je faire avec cet ensemble d'ingrédients?»

11

Formellement, soit s ( U , Q ) = { V | VU et VQ } où U , Q et V représentent tous des ensembles, et U , plus spécifiquement, représente un ensemble d'ensembles. Par exemple, U pourrait être un ensemble des (ensembles) d'ingrédients requis pour diverses recettes dans un livre de cuisine avec Q représentant l'ensemble des ingrédients que j'ai V représentant une recette que je pourrais faire avec ces ingrédients. La requête s ( U , Q) correspond à la question "Que puis-je faire avec ces ingrédients?"

Ce que je recherche, c'est une représentation des données qui indexe U de manière à prendre en charge des requêtes efficaces de s ( U , Q ) où Q et tous les membres de U seront généralement petits par rapport à l'union de tous les membres de U . De plus, j'aimerais qu'il puisse mettre à jour efficacement U (par exemple, ajouter ou supprimer une recette).

Je ne peux pas m'empêcher de penser que ce problème doit être bien compris, mais je n'ai pas été en mesure de trouver un nom ou une référence pour cela. Quelqu'un connaît-il une stratégie pour résoudre ce problème efficacement ou un endroit où je peux en savoir plus à ce sujet?

En ce qui concerne la réflexion sur une solution, on pensait que je devais était de construire un arbre de décision pour l'ensemble U . À chaque nœud de l'arbre, la question "votre liste d'ingrédients contient-elle x ?" serait demandé avec x choisi pour maximiser le nombre de membres de U qui sont éliminés par la réponse. Au fur et à mesure que U est mis à jour, cet arbre de décision devrait être rééquilibré pour minimiser le nombre de questions nécessaires pour trouver le résultat correct. Une autre pensée est de représenter U avec quelque chose comme un «octree» booléen à n dimensions (où n est le nombre d'ingrédients uniques).

Je crois que "Quelles recettes peuvent être faites avec ces ingrédients?" peut être répondu en prenant le produit cartésien des (ensembles d'ingrédients requis pour les) recettes dans le livre de cuisine avec le jeu de puissance des ingrédients que l'on a et en filtrant les paires ordonnées résultantes pour les paires dans lesquelles les deux éléments sont égaux, mais ce n'est pas un solution efficace, et ce que je demande, c'est comment optimiser ce type d'opération; comment pourrait-on composer cela en SQL de manière à ce qu'il soit efficace et que fait SQL pour que cela soit efficace?

Bien que j'utilise l'illustration d'un livre de recettes de recettes et d'un ensemble d'ingrédients, je prévois que le nombre de «recettes» et le nombre d '«ingrédients» seront très importants (jusqu'à des centaines de milliers chacun), bien que le nombre d'ingrédients dans une recette donnée et le nombre d'ingrédients dans un ensemble d'ingrédients donné sera relativement faible (probablement environ 10 à 50 pour une «recette» typique et environ 100 pour un «ensemble d'ingrédients» typique). De plus, l'opération la plus courante sera la requête s ( U , Q ), elle devrait donc être la plus optimale. Cela signifie également qu'un algorithme de force brute qui nécessite de vérifier chaque recette ou d'opérer sur chaque ingrédient serait cependant trop lent en soi. Avec une mise en cache intelligente,

nben
la source
1
Un problème qui devrait être facilement résolu avec une base de données SQL.
Robert Harvey
1
Sur la base de votre description supplémentaire, cela ressemble à un problème à l'échelle Orbitz. Le moteur de recherche d'Orbitz utilise un moteur Lisp qui passe en revue un milliard de points de données pour obtenir une liste de vols qui conviendront à votre itinéraire spécifique. Son exigence non fonctionnelle est qu'il doit renvoyer une solution en 10 secondes ou moins. Voir ici paulgraham.com/carl.html , mais notez que les informations sont assez anciennes.
Robert Harvey
Cette question est assez large et comporte deux parties: une structure de données et un algorithme pour trouver des recettes existantes qui sont des sous-ensembles d'ingrédients, et comment les adapter à des données volumineuses. Je pense que cela devrait être deux questions. Vous ne pouvez pas vraiment traiter la grande partie des données tant que vous n'avez pas réduit la partie de l'algorithme. user16054 a déjà obtenu de l'aide pour savoir comment les tables de jointure sont utilisées dans une représentation de base de données relationnelle. Si cette question est restreinte à la partie algorithme / infrastructure de données, ou si une autre question indépendante a été posée, je pourrai peut-être faire des suggestions.
rocky

Réponses:

4

Pour les chiffres que vous avez donnés, forcez-les simplement.

Voici un programme JavaScript qui le force à forcer 10 ingrédients dans la base de données, 10 recettes dans la base de données, chaque recette a besoin de 2 ingrédients et j'ai 5 ingrédients disponibles:

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

Il s'exécute en 0 millisecondes. J'ai choisi ces petits nombres afin que vous puissiez l'exécuter vous-même plusieurs fois et vous convaincre qu'il fait ce que vous voulez et qu'il est relativement exempt de bogues.

Maintenant changez-le pour que nous ayons 1'000'000 ingrédients dans la DB, 1'000'000 recettes dans la DB, 50 ingrédients par recette et 100 ingrédients à ma disposition. C'est-à-dire des valeurs qui sont toutes égales ou supérieures au plus grand cas d'utilisation que vous avez donné.

Il s'exécute en 125 millisecondes sous nodejs, et c'est avec l'implémentation la plus stupide sans aucun effort d'optimisation.

Nebu Pookins
la source
1
À moins que les exigences du PO ne changent, il n'y a aucune raison de ne pas adopter ce type d'approche. Une structure de données intelligente? Non, assez vite? Oui. Maintenable et facile à comprendre? Très certainement.
J Trana