Python itertools.groupby со словарями с несколькими значениями
Я пытаюсь использовать функцию Python itertools.groupby, чтобы изменить этот список:
items = [
{'price': 5.0, 'name': 'Strawberries'},
{'price': 5.0, 'name': 'Strawberries'},
{'price': 5.0, 'name': 'Strawberries'},
{'price': 11.23, 'name': 'Coffee'},
{'price': 11.23, 'name': 'Coffee'},
{'price': 3.11, 'name': 'Green Tea'}
]
в это:
{
'Strawberries': {'price': 5.0, 'quantity': 3},
'Coffee': {'price': 11.23, 'quantity': 2},
'Green Tea': {'price': 3.11, 'quantity': 1}
}
Я пробовал оба:
grouped = {
name: {
'price': list(article)[0]['price'],
'quantity': len(list(article))
} for name, article in groupby(items, key=lambda x: x['name'])
}
а также:
grouped = {
name: {
'quantity': list(article),
'price': list(article)[0]['price']
} for name, article in groupby(items, key=lambda x: x['name'])
}
со следующими результатами:
{
'Strawberries': {'price': 5.0, 'quantity': []},
'Coffee': {'price': 11.23, 'quantity': []},
'Green Tea': {'price': 3.11, 'quantity': []}
}
IndexError: list index out of range
Я не уверен, почему я могу получить доступ к статье только для одного из значений в подразделе, который я пытаюсь создать.
Любые предложения будут высоко ценится. Спасибо!
2 ответа
Не лучший вариант использования для
groupby
я считаю. Легче построить
(default)dict
с петлей
items
.
from collections import defaultdict
result = defaultdict(lambda: {'price': None, 'quantity': 0})
for item in items:
subdict = result[item['name']]
subdict['quantity'] += 1
subdict['price'] = item['price']
Выход:
>>> result
defaultdict(<function __main__.<lambda>()>,
{'Strawberries': {'price': 5.0, 'quantity': 3},
'Coffee': {'price': 11.23, 'quantity': 2},
'Green Tea': {'price': 3.11, 'quantity': 1}})
(Цена перекрывается последней увиденной ценой товара. Это нормально, если вы не ожидаете двусмысленных цен на товары с одинаковым названием.)
редактировать: без
defaultdict
result = {}
for item in items:
result.setdefault(item['name'], {'price': item['price'], 'quantity': 0})['quantity'] += 1
Причина, по которой вы получаете пустой список или ошибку индекса, заключается в том, что ваш объект является итератором, который полностью используется при первом вызове .
Когда вы сначала получаете цену, цена верна, но количество является пустым списком, потому что вы уже потребляете
article
. Напротив, когда вы сначала получаете количество, а затем берете цену первого элемента, второй вызов создает пустой список, который вы пытаетесь проиндексировать, но не можете, потому что элементов нет.
Вот решение с
groupby
где вы сохраняете
list(article)
и использовать его как для цены, так и для количества.
grouped = {}
for name, article in groupby(items, key=lambda itm: itm["name"]):
products = list(article)
grouped[name] = {
"price": products[0]["price"],
"quantity": len(products),
}
Изменить: как упоминалось в комментариях, это предполагает, что ваш
items
список в том порядке, в котором вы хотите. Часто вы захотите, чтобы итерация была передана
groupby()
быть отсортированы по смыслу. Но, возможно, вы просто хотите сгруппировать последовательные элементы вместе, даже если один и тот же элемент встречается позже в вашем списке.