collections: Counter, defaultdict, deque и дедупликация

Готовые структуры из модуля collections и две частые задачи: частота элементов и дедупликация без потери порядка.

Модуль collections даёт специализированные контейнеры: Counter (счётчик), defaultdict (словарь со значением по умолчанию), deque (двусторонняя очередь).

Задача: частота слов через Counter

Чёткий ответ. Counter из модуля collections считает вхождения автоматически, а most_common(n) возвращает n самых частых элементов уже отсортированными.

from collections import Counter

text = "ля ля фа ля фа соль фа"
words = text.split()
freq = Counter(words)
print(freq)
print("топ-2:", freq.most_common(2))

Вывод:

Counter({'ля': 3, 'фа': 3, 'соль': 1})
топ-2: [('ля', 3), ('фа', 3)]

Counter для символов

Тот же приём работает для любой последовательности — например, посчитать буквы в слове.

from collections import Counter
print(Counter("mississippi"))
print(Counter("mississippi").most_common(2))

Вывод:

Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
[('i', 4), ('s', 4)]

defaultdict: группировка без проверок

Обычный словарь бросает KeyError при доступе к несуществующему ключу. defaultdict(list) сам создаёт пустой список для нового ключа — идеально для группировки, избавляя от if key not in d: d[key] = [].

from collections import defaultdict

groups = defaultdict(list)
for word in ["apple", "banana", "avocado", "cherry"]:
    groups[word[0]].append(word)     # ключа ещё нет — создастся []

print(dict(groups))

Вывод:

{'a': ['apple', 'avocado'], 'b': ['banana'], 'c': ['cherry']}

Задача: дедупликация с сохранением порядка

Просто set() убирает дубликаты, но теряет порядок. Чтобы сохранить порядок первого появления, ведут множество «уже виденных» — или используют тот факт, что словарь с Python 3.7 хранит порядок вставки.

def dedup(seq):
    seen = set()
    result = []
    for x in seq:
        if x not in seen:
            seen.add(x)
            result.append(x)
    return result

data = [3, 1, 2, 1, 3, 4, 2]
print("вручную:    ", dedup(data))
# короткий идиоматичный способ — dict.fromkeys хранит порядок вставки
print("fromkeys:   ", list(dict.fromkeys(data)))

Вывод:

вручную:     [3, 1, 2, 4]
fromkeys:    [3, 1, 2, 4]

dict.fromkeys создаёт словарь с уникальными ключами в порядке их первого появления; превратив его ключи в список, получаем дедупликацию с сохранением порядка одной строкой.

deque: быстрая очередь с двух концов

У списка pop(0) и insert(0, x) работают за O(n). deque добавляет и удаляет с обоих концов за O(1) — правильный выбор для очередей и стеков.

from collections import deque

dq = deque([1, 2, 3])
dq.appendleft(0)      # O(1) слева
dq.append(4)          # O(1) справа
print(dq)
print("слева:", dq.popleft(), "справа:", dq.pop())
print(dq)

Вывод:

deque([0, 1, 2, 3, 4])
слева: 0 справа: 4
deque([1, 2, 3])

Итог

  • Counter(iterable) считает частоты за один проход; most_common(n) отдаёт самые частые.
  • defaultdict(list) избавляет от проверок ключа при группировке.
  • deque — очередь с O(1) на обоих концах; дедупликацию с порядком даёт list(dict.fromkeys(seq)).
Проверьте себя
1. Что делает Counter(words).most_common(2)?
AСортирует все слова
BВозвращает 2 самых частых элемента с их числом
CУдаляет дубликаты
DСчитает длину
2. Зачем нужен defaultdict(list)?
AУскоряет словарь
BАвтоматически создаёт пустой список для нового ключа, избавляя от проверок
CСортирует ключи
DЗапрещает добавлять ключи
3. Какое преимущество deque перед list для очереди?
AМеньше памяти
Bpopleft/appendleft за O(1) вместо O(n) у list
Cdeque хранит порядок, а list нет
Ddeque потокобезопаснее
4. Что вернёт list(dict.fromkeys([3,1,2,1,3]))?
A[1, 2, 3]
B[3, 1, 2]
C[3, 1, 2, 1, 3]
D[3, 3, 1, 1, 2]
Поддержать проект