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()быть отсортированы по смыслу. Но, возможно, вы просто хотите сгруппировать последовательные элементы вместе, даже если один и тот же элемент встречается позже в вашем списке.

Другие вопросы по тегам