The Translator Pattern is what you're asking for.
But I suspect what you're looking for is a framework, more than a pattern. I believe Dozer is popular in the Java world.
For the numbers you gave, just brute force it.
Here's a JavaScript program that brute forces it for 10 ingredients in the DB, 10 recipes in the DB, each recipe needs 2 ingredients, and I have 5 ingredients available:
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);
It runs in 0 milliseconds. I chose these small numbers so you could run it yourself a couple of times and convince yourself it does what you want and is relatively free of bugs.
Now change it so that we have 1'000'000 ingredients in the DB, 1'000'000 recipes in the DB, 50 ingredients per recipe and 100 ingredients available to me. I.e. values that are all equal or greater than the largest use case you gave.
It runs in 125 milliseconds under nodejs, and this is with the dumbest implementation with absolutely no effort to optimize.
Best Answer
You'll need to keep track of distances in both directions, not just one. Ie, Abernathy to Gruver, and then Gruver will also have Abernathy's distance.