Как записать результаты операции агрегирования в pymongo как один документ в другой коллекции - PullRequest
0 голосов
/ 10 апреля 2019

Хорошо, я хочу создать какой-то алгоритм MapReduce для создания обратного индекса для текстовых документов. В части картирования я делаю что-то вроде этого

letters = ['a']
regx = re.compile("^("+"|".join(letters)+')')
selectedWords = directIndex.aggregate([
    { "$match": { "words.word": regx } },
    { "$unwind": "$words" },
    { "$match": { "words.word": regx } },
    { "$group": { "_id": { "word":"$words.word", "count":"$words.count", 'document' : '$document' } } }])

Хорошо, здесь я выбираю все слова и информацию, связанную с ними, по первой букве. После этого я записываю эту информацию в другую коллекцию:

myinvcol.insert_one({'letter':str(''.join(letters)),'words':selectedWords })

На следующем шаге я читаю каждый вставленный документ и выполняю операцию dict dict ('wordName': {documents: [document1: count1, document2: count2 и т. Д.], 'WordName2: {documents: [...] } ') и выполните некоторые дополнительные операции над этим диктом

Теперь самое интересное)): Можно ли сделать первый шаг (часть карты) или агрегацию, чтобы полностью выполнить на сервере MongoDB? Другими словами, я знаю, что есть оператор $ out:

letters = ['a']
regx = re.compile("^("+"|".join(letters)+')')
selectedWords = directIndex.aggregate([
    { "$match": { "words.word": regx } },
    { "$unwind": "$words" },
    { "$match": { "words.word": regx } },
    { "$group": { "_id": { "word":"$words.word", "count":"$words.count", 'document' : '$document' } } }
    { "$out" : 'InverseIndex'}])

Позволяет мне записать результат агрегата в другую коллекцию, но не выполняет то, что я хочу: вместо вставки одного документа:

{'letter':str(''.join(letters)),'words':selectedWords }, 

я получил много вставок

{ "_id": { "word":"$words.word", "count":"$words.count", 'document' : '$document' } }. 

Итак, в заключение, существует ли способ создать документ в агрегации, который объединит все его результаты в одном массиве перед оператором $ out?

1 Ответ

0 голосов
/ 10 апреля 2019

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

regx = re.compile("^("+"|".join('ab')+')')
myinvcol.insertMany(mydb.runCommand(
{
 'aggregate': "DirectIndex",
    'pipeline': 
    [
    { "$match": { "words.word": regx } },
    { "$unwind": "$words" },
    { "$match": { "words.word": regx } },
    { "$group": { "_id": { "word":"$words.word", "count":"$words.count", 'document' : '$document' } } },
    { "$group": {
        "_id": {'$substr':[''.join('ab'),0,len(''.join('ab'))]},
        "words": {
            "$push": {
                "word": "$_id.word",
                "count":"$_id.count",
                'document' : '$_id.document'
            }
        }
    }},
    {'$out':"InverseIndex"}
]}).result)

(находится здесь mongoDB: как развернуть $ unwind ) Но здесь Монго отстой. Параметр out перезаписывает содержимое коллекции. Так что, если я позвоню больше одного раза, предыдущий результат исчезнет Как я вижу здесь: Как добавить результаты агрегации БД Mongo к существующей коллекции? , Mongo 4.2 будет иметь специальный параметр для $ out, называемый mode: «replaceDocuments». Это позволит вам добавлять новый контент в вашу коллекцию. Но пока мертвая идея.

Ну, я попытался сделать это с помощью встроенной функции mongo map_reduce:

mape = Code("function () {"
    "var docName =this.document;"
                   "this.words.forEach(function(z) {"
                   "z['document'] = docName;"
                   "var temp = z.word;"
                   "delete z.word;"
    "    emit(temp, {'documents':[z]});"
    "  });"
    "}")
reduce = Code("function (key, values) {"
           "  var total = [];"
           "  for (var i = 0; i < values.length; i++) {"
           "for (var j=0;j<values[i]['documents'].length;j++){"
                "total.push({'document':values[i]['documents'][j]['document'], 'count':values[i]['documents'][j]['count'], 'tf':values[i]['documents'][j]['tf']});"
           "  }}"
           "  return {'documents': total};"
       "}")
finalizeFunction = Code("function (key, reducedVal) {"
        "if('documents' in reducedVal){"
            "var normVal = Math.log((1+"+str(nrDocs)+")/(1+1+reducedVal.documents.length));"
            "reducedVal['idf']=normVal;"
            "return reducedVal;} else{ return null;}"
        "};")
result = mydb.DirectIndex.map_reduce(mape, reduce, {'merge':"InverseIndex"},finalize=finalizeFunction)

Это как-то делает то, что мне нужно. Недостатком является скорость. По сравнению с MapReduce, реализованным вручную (агрегация + отображение по dict, ключом которого является слово), разница довольно большая. Во всяком случае, если кто-то сталкивается с этой проблемой, я знаю только эти 2 способа ее решения.

...