"이 재료 세트로 어떤 레시피를 만들 수 있습니까?"에 대답하는 알고리즘 / 데이터 구조


11

공식적으로 s ( U , Q ) = { V | VUVQ } 여기서 U , QV는 모두 세트를 나타내고, U 는보다 구체적으로 세트 세트를 나타냅니다. 예를 들어, U 는 요리 책에서 다양한 요리법에 필요한 (세트 세트) 재료 세트 일 수 있습니다. Q 는 재료 세트를 나타내는 V 를 가지고 있습니다 . 쿼리 s ( U , Q)는 "이 재료로 무엇을 만들 수 있습니까?"라는 질문에 해당합니다.

내가 찾고 인덱스하는 데이터 표현 U 등의 방법으로 그것을 효율적으로 쿼리를 지원하는 S ( U , Q ) Q 와의 모든 구성원 U는 일반적으로 구성원 모두의 조합에 비해 작은 것 U를 . 또한 U 를 효율적으로 업데이트 (예 : 레시피 추가 또는 제거) 할 수 있기를 바랍니다 .

나는이 문제를 잘 이해해야한다고 생각할 수는 없지만 이름이나 참조를 찾을 수 없었습니다. 이것을 효율적으로 해결하기위한 전략이나 그것에 대해 더 많이 읽을 수있는 곳을 아는 사람이 있습니까?

솔루션에 대해 생각하는 한, 세트 U에 대한 의사 결정 트리를 작성해야한다고 생각했습니다 . 트리의 각 노드에서 "성분 목록에 x가 포함되어 있습니까?"라는 질문이 있습니다. 답에 의해 제거되는 U 의 구성원 수를 최대화하기 위해 x를 선택 하도록 요청 합니다. 으로 U가 업데이트 될 때,이 의사 결정 나무는 다시 균형 올바른 결과를 찾기 위해 필요한 질문의 수를 최소화 할 필요가있다. 또 다른 생각은 Un 차원 부울 'octree'(여기서 n 은 고유 성분의 수) 와 같은 것으로 표현 하는 것 입니다.

"이 재료로 어떤 요리법을 만들 수 있습니까?" 요리 책에있는 요리법에있는 (필요한 재료 세트) 레시피의 데카르트 곱을 가지고있는 재료의 파워 세트로 가져 와서 두 요소가 동일한 쌍에 대해 결과적인 순서 쌍을 필터링함으로써 대답 할 수 있습니다. 효율적인 솔루션과 내가 요구하는 것은 이런 종류의 작업을 최적화하는 방법입니다. 어떻게 SQL에서 효율적으로 작성하고 효율적으로 수행 할 수 있습니까?

요리법 요리법과 재료 세트의 삽화를 사용하지만 재료의 수는 많지만 '레시피'와 '성분'의 수는 매우 클 것으로 예상됩니다. 주어진 레시피에서 주어진 성분 세트의 성분 수는 상대적으로 작을 것입니다 (아마도 전형적인 '레시피'의 경우 약 10-50, 전형적인 '성분 성분'의 경우 약 100). 또한, 가장 일반적인 작업은 쿼리 될 것 S ( U , Q를 가 최적해야하므로). 이것은 또한 모든 레시피를 확인하거나 모든 성분에 대해 작동해야하는 무차별 강제 알고리즘이 바람직하지 않게 느리다는 것을 의미합니다. 영리한 캐싱으로


1
SQL 데이터베이스로 쉽게 해결할 수있는 문제입니다.
Robert Harvey

1
추가 설명에 따르면 Orbitz 규모의 문제처럼 들립니다. Orbitz의 검색 엔진은 Lisp 엔진을 사용하여 수십억 개 정도의 데이터 포인트를 검색하여 특정 여정에 적합한 항공편 목록을 얻습니다. 작동하지 않는 것은 10 초 이내에 솔루션을 반환해야한다는 것입니다. 정보는 상당히 오래되었지만 여기에서 paulgraham.com/carl.html을 참조하십시오 .
Robert Harvey

이 질문은 상당히 광범위하며 두 부분으로 구성됩니다. 재료의 하위 집합 인 기존 레시피를 찾기위한 데이터 구조와 알고리즘 및 큰 데이터를 위해이를 확장하는 방법. 제 생각에는 이것이 두 가지 질문이어야한다는 것입니다. 알고리즘 부분을 좁힐 때까지 실제로 큰 데이터 부분을 처리 할 수 ​​없습니다. user16054는 관계형 데이터베이스 표현에서 조인 테이블이 사용되는 방법에 대한 도움말을 이미 얻었습니다. 이 질문이 알고리즘 / 데이터 구조 부분으로 좁혀 지거나 다른 독립적 인 질문이있는 경우 제안을 제안 할 수 있습니다.
rocky

답변:


4

당신이 준 숫자에 대해서는 그냥 무차별하게하십시오.

다음은 DB의 10 개 재료, DB의 10 개 레시피, 각 레시피에 2 개의 재료가 필요하며 5 개의 재료를 사용할 수있는 무차별 JavaScript 프로그램입니다.

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 밀리 초로 실행되며 이는 최적화하려는 노력없이 dumbest 구현입니다.


1
OP의 요구 사항이 변경되지 않는 한 이런 종류의 접근 방식을 취하지 않을 이유는 없습니다. 영리한 데이터 구조? 아뇨. 빨리요? 예. 유지 보수가 쉽고 이해하기 쉬운가? 가장 확실합니다.
J Trana
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.