← все задачи

Python · задача 1 из 10

Сгруппировать записи по полю

Начальный 10 минут словариdefaultdictкраевые случаи

Условие

Есть список заказов, у каждого есть customer_id и сумма. Напишите функцию, которая вернёт словарь: клиент — список его заказов. Затем — вариант, который вернёт сумму заказов по каждому клиенту.

Что требуется

  • На вход — список словарей произвольной длины
  • Порядок заказов внутри клиента сохраняется
  • Пустой список — пустой результат, без исключения

Пример

orders = [
    {"customer_id": 1, "amount": 100},
    {"customer_id": 2, "amount": 50},
    {"customer_id": 1, "amount": 30},
]

group_by_customer(orders)
# {1: [{...100}, {...30}], 2: [{...50}]}

total_by_customer(orders)
# {1: 130, 2: 50}

Сначала уточните

Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.

  • Может ли customer_id отсутствовать в записи — пропускать такие или падать?
  • Нужен ли порядок ключей в результате (тогда dict сохранит порядок появления)?
  • Объём данных: помещается в память или это поток на миллионы строк?
Показать решение Скрыть решение

Решение

from collections import defaultdict


def group_by_customer(orders):
    grouped = defaultdict(list)
    for order in orders:
        grouped[order["customer_id"]].append(order)
    return dict(grouped)


def total_by_customer(orders):
    totals = defaultdict(int)
    for order in orders:
        totals[order["customer_id"]] += order["amount"]
    return dict(totals)


# Если группировать нужно по произвольному признаку:
def group_by(items, key):
    grouped = defaultdict(list)
    for item in items:
        grouped[key(item)].append(item)
    return dict(grouped)

Почему так

Почему defaultdict, а не dict.setdefault

  • setdefault создаёт новый список на каждой итерации, даже когда он не нужен, — defaultdict делает это только при промахе
  • Код читается как формула: «взять список этого клиента и добавить заказ», без служебной ветки
  • dict(grouped) на выходе — чтобы наружу не утекал объект, который молча создаёт ключи при чтении: grouped[999] вернёт [] и добавит ключ

Почему не itertools.groupby

  • groupby группирует только подряд идущие элементы — для несортированного списка он даст несколько групп на один ключ
  • Чтобы им воспользоваться, список нужно сначала отсортировать: O(n log n) вместо O(n)
  • Назвать это вслух — хороший ход: видно, что вы знаете инструмент и понимаете, почему он здесь не подходит

Что спросят дальше

  • Спросят про отсутствующий ключ: order.get("customer_id") и явное решение — пропустить, положить в None-группу или упасть
  • Спросят про большие данные: если это поток, показывайте генератор и агрегацию на лету, а не список в памяти
  • Могут попросить топ-3 клиентов по сумме — это sorted(totals.items(), key=lambda kv: -kv[1])[:3] или heapq.nlargest

Следующая задача

Топ-N самых частых слов — Проверяют знание Counter и умение аккуратно обойтись с одинаковыми частотами.