Mongo: подсчитать количество слов в наборе документов

У меня есть набор документов в Монго. Скажи:

[
    { summary:"This is good" },
    { summary:"This is bad" },
    { summary:"Something that is neither good nor bad" }
]

Я хотел бы подсчитать количество вхождений каждого слова (без учета регистра), а затем сортировать в порядке убывания. Результат должен выглядеть примерно так:

[
    "is": 3,
    "bad": 2,
    "good": 2,
    "this": 2,
    "neither": 1,
    "nor": 1,
    "something": 1,
    "that": 1
]

Есть идеи, как это сделать? Агрегационная структура была бы предпочтительнее, поскольку я уже в некоторой степени понимаю это:)

Ответ 1

MapReduce может быть подходящим для обработки документов на сервере без манипуляций с клиентом (так как нет возможности разбивать строку на сервер БД (открытая проблема).

Начните с функции map. В приведенном ниже примере (который, вероятно, должен быть более надежным) каждый документ передается функции map (как this). Код ищет поле summary, и если он там, уменьшает его, разбивает на пробел и затем испускает 1 для каждого найденного слова.

var map = function() {  
    var summary = this.summary;
    if (summary) { 
        // quick lowercase to normalize per your requirements
        summary = summary.toLowerCase().split(" "); 
        for (var i = summary.length - 1; i >= 0; i--) {
            // might want to remove punctuation, etc. here
            if (summary[i])  {      // make sure there something
               emit(summary[i], 1); // store a 1 for each word
            }
        }
    }
};

Затем в функции reduce он суммирует все результаты, найденные функцией map, и возвращает дискретное значение для каждого слова, которое было emit ted выше.

var reduce = function( key, values ) {    
    var count = 0;    
    values.forEach(function(v) {            
        count +=v;    
    });
    return count;
}

Наконец, выполните mapReduce:

> db.so.mapReduce(map, reduce, {out: "word_count"})

Результаты с вашими данными:

> db.word_count.find().sort({value:-1})
{ "_id" : "is", "value" : 3 }
{ "_id" : "bad", "value" : 2 }
{ "_id" : "good", "value" : 2 }
{ "_id" : "this", "value" : 2 }
{ "_id" : "neither", "value" : 1 }
{ "_id" : "or", "value" : 1 }
{ "_id" : "something", "value" : 1 }
{ "_id" : "that", "value" : 1 }

Ответ 2

Основной пример MapReduce

var m = function() {
    var words = this.summary.split(" ");
    if (words) {
        for(var i=0; i<words.length; i++) {
            emit(words[i].toLowerCase(), 1);
        }   
    }
}

var r = function(k, v) {
    return v.length;
};

db.collection.mapReduce(
    m, r, { out: { merge: "words_count" } }
)

Это вставляет количество слов в имя коллекции words_count, которое вы можете сортировать (и индексировать)

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

Также обратите внимание, что вы можете оптимизировать функцию карты, накапливая повторяющиеся слова (-ы) и испуская счет, а не только 1