Вы в основном подходите к этому неправильно. У вас есть четкий путь к "b.d"
в качестве ключа, по которому вы хотите агрегировать, нет необходимости преобразовывать его в массив:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": None,
"data": { "$push": { "k": "$_id", "v": "$count" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
for doc in cursor:
print(doc)
Возвращает
{ 'x': 1, 'xx': 2, 'xxx': 1 }
Но это на самом деле излишне, поскольку в действительности вся работа была проделана в этом первоначальном утверждении $group
. Все, что вам действительно нужно сделать, это запустить его, извлечь результаты и объединить их в один словарь в качестве желаемого результата:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}}
])
data = list(cursor)
result = reduce(
lambda x,y:
dict(x.items() + { y['_id']: y['count'] }.items()), data,{})
print(result)
Что возвращает точно то же самое:
{ 'x': 1, 'xx': 2, 'xxx': 1 }
Более того, он делает это без гимнастики , требуемой путем добавления других этапов агрегирования и операторов, и вы не изменили то, что действительно возвращается с сервера, начиная с начальных $group
Ответ в основном:
{ "_id" : "xxx", "count" : 1 }
{ "_id" : "xx", "count" : 2 }
{ "_id" : "x", "count" : 1 }
Итак, настоящий урок здесь - пока вы можете делать причудливые манипуляции в конвейере агрегации, что вам действительно следует учитывать, так это то, что вы , вероятно, не должны , когда альтернатива чище и намного более читаемый код.
Для справки, все, что происходит, это дополнительные $group
, использующие $push
для создания массива с ключами k
и v
, как и следовало ожидать в следующий этап трубопровода. Там, где на следующем этапе используется $replaceRoot
, чтобы взять вывод $arrayToObject
из этого массива, созданного на предыдущем этапе, и в основном преобразует его в объект / словарь.
В отличие от этого reduce
делает то же самое. Мы в основном принимаем результаты курсора в list
, чтобы функции python могли действовать в этом списке. Тогда это просто вопрос обхода элементов в этом списке, которые всегда имеют _id
в качестве ключа, и другого именованного свойства для «подсчитанного» вывода (здесь мы использовали count
) и простого преобразования их в ключ и пары значений для окончательного вывода словаря.
Ради интереса кое-что, основанное на ваших первоначальных попытках, может быть:
db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": { "$toString": "$_id.k" }, "v": "$count" } }
}},
{ "$addFields": {
"data": { "$arrayToObject": "$data" }
}}
])
Что вернет:
{ "_id" : "c", "data" : { "25" : 3, "5" : 1 } }
{ "_id" : "e", "data" : { "36" : 4 } }
{ "_id" : "d", "data" : { "xxx" : 1, "xx" : 2, "x" : 1 } }
Опять же, тот же результат без дополнительных этапов конвейера для преобразования получается при использовании map
и reduce
с python:
cursor = db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": "$_id.k", "v": "$count" } }
}}
])
data = list(cursor)
result = map(lambda d: {
'_id': d['_id'],
'data': reduce(lambda x,y:
dict(x.items() + { y['k']: y['v'] }.items()), d['data'],
{})
},data)