2016-09-20 2 views
2

У меня есть база данных с столбцом, содержащим массив строк. Пример таблицы:Как сгруппировать документы, сопоставляя элементы массива с MapReduce в MongoDB?

name | words       | ... 
Ash | ["Apple", "Pear", "Plum"]  | ... 
Joe | ["Walnut", "Peanut"]   | ... 
Max | ["Pineapple", "Apple", "Plum"] | ... 

Теперь я хотел бы, чтобы соответствовать этой таблице против данного массива слов и группировать документы по скорости их согласования.

Пример входных данных с ожидаемым результатом:

// matched for input = ["Walnut", "Peanut", "Apple"] 
{ 
    "1.00": [{name:"Joe", match:"1.00"}], 
    "0.33": [{name:"Ash", match:"0.33"}, {name:"Max", match:"0.33"}] 
} 

Я использую следующую map функцию, испускающий документ со скоростью согласования в качестве ключа:

function map() { 
    var matches = 0.0; 
    for(var i in input) 
     if(this.words.indexOf(input[i]) !== -1) matches+=1; 
    matches /= input.length; 
    var key = ""+matches.toFixed(2); 
    emit(key, {name: this.name, match: key}); 
} 

Теперь недостающего является соответствие reduce функция объединить излучаемые пары KV в объект результата.

Я пробовал так:

function reduce(key, value) { 
    var res = {}; 
    res[key] = values; 
    return res; 
} 

Однако у меня есть проблемы с уточнением, что

MongoDB может вызвать функцию уменьшения более чем один раз для одной и той же ключа. В этом случае предыдущий вывод функции уменьшения для , что ключ станет одним из входных значений для следующего уменьшения функции вызова для этого ключа.

... в результате чего возникают объекты вложенных результатов. Каков правильный способ группировки документов по их совпадению?

ответ

1

вызов функции уменьшения более одного раза для одного и того же ключа.

Это idempotence, и функция уменьшения должна уважать это.

Но, чтобы сделать это просто, вам нужно только убедиться, что выход карты находится в том же формате, что и сокращение.

В вашем случае, что-то, как это будет работать:

db.col.insert({"name": "Ash", "words": ["Apple", "Pear", "Plum"]}) 
db.col.insert({"name": "Joe", "words": ["Walnut", "Peanut"]}) 
db.col.insert({"name": "Max", "words": ["Pineapple", "Apple", "Plum"]}) 

function map() { 

    input = ["Walnut", "Peanut", "Apple"] 

    var matches = 0.0; 
    for(var i in input) 
     if(this.words.indexOf(input[i]) !== -1) matches+=1; 
    matches /= input.length; 
    var key = ""+matches.toFixed(2); 

    emit(key, {users: [{name: this.name, match: key}]}); 
} 

function reduce(key, value) { 

    ret = value[0] 

    for(var i=1; i<value.length; i++){ 
     ret.users = ret.users.concat(value[i].users) 
    } 

    return ret 

} 

db.col.mapReduce(map, reduce, {"out": {inline:1}}) 

Выход:

{ 
    "results" : [ 
     { 
      "_id" : "0.33", 
      "value" : { 
       "users" : [ 
        { 
         "name" : "Ash", 
         "match" : "0.33" 
        }, 
        { 
         "name" : "Max", 
         "match" : "0.33" 
        } 
       ] 
      } 
     }, 
     { 
      "_id" : "0.67", 
      "value" : { 
       "users" : [ 
        { 
         "name" : "Joe", 
         "match" : "0.67" 
        } 
       ] 
      } 
     } 
    ], 
    "timeMillis" : 22, 
    "counts" : { 
     "input" : 3, 
     "emit" : 3, 
     "reduce" : 1, 
     "output" : 2 
    }, 
    "ok" : 1 
} 
+1

Спасибо, это было именно то, что я был после. Очень полезный ответ! – Appleshell

Смежные вопросы