SprintCode.pro

Подготовка к алгоритмическим задачам

Super

Модуль collections в Python: Counter, defaultdict, deque и другие

11 мин чтения
python
структуры данных

Коротко

КлассЗачем
Counterподсчёт элементов
defaultdictсловарь со значением по умолчанию
dequeочередь с двух концов за O(1)
namedtupleкортеж с именованными полями
OrderedDictсловарь с операциями над порядком
ChainMapобъединение словарей без копирования

Модуль collections — стандартная библиотека, ничего устанавливать не нужно. Знание этих шести классов заметно сокращает код в алгоритмических задачах.

Counter: подсчёт за одну строку

Самый полезный класс модуля. Считает, сколько раз встретился каждый элемент.

from collections import Counter counts = Counter('абракадабра') print(counts) # Counter({'а': 5, 'б': 2, 'р': 2, 'к': 1, 'д': 1}) print(counts['а']) # 5 print(counts['я']) # 0 — отсутствующий ключ не вызывает ошибку

Последняя строка важна: Counter возвращает ноль вместо KeyError. Это избавляет от постоянных проверок.

Полезные методы

counts = Counter('абракадабра') counts.most_common(3) # [('а', 5), ('б', 2), ('р', 2)] counts.most_common() # все, по убыванию частоты sum(counts.values()) # 11 — общее количество list(counts.elements()) # развернуть обратно в элементы

most_common(k) внутри использует кучу и работает за O(n log k) — быстрее полной сортировки при малом k.

Арифметика счётчиков

a = Counter('aabbc') b = Counter('abbbd') print(a + b) # сложение частот print(a - b) # вычитание, отрицательные отбрасываются print(a & b) # минимум по каждому ключу (пересечение) print(a | b) # максимум по каждому ключу (объединение)

Операция & решает задачу «сколько общих букв у двух строк» одной строкой.

Типичные применения

undefined
Пройди собеседование в топ-компанию
Платформа для подготовки

Решай алгоритмические задачи как профи

✓ Популярные алгоритмы✓ Разбор решений✓ AI помощь
Начать сейчас
Программист за работой

анаграммы: строки равны как мультимножества

def is_anagram(a: str, b: str) -> bool: return Counter(a) == Counter(b)

самые частые элементы

def top_k(nums: list, k: int) -> list: return [x for x, _ in Counter(nums).most_common(k)]

есть ли дубликаты

def has_duplicates(nums: list) -> bool: return any(c > 1 for c in Counter(nums).values())

print(is_anagram('листок', 'столик')) # True print(top_k([1, 1, 1, 2, 2, 3], 2)) # [1, 2]


## defaultdict: словарь без проверок

Обычный словарь падает при обращении к несуществующему ключу. `defaultdict` вместо этого создаёт значение по умолчанию.

```python
from collections import defaultdict

# было
graph = {}
for u, v in edges:
    if u not in graph:
        graph[u] = []
    graph[u].append(v)

# стало
graph = defaultdict(list)
for u, v in edges:
    graph[u].append(v)

Аргумент — фабрика, то есть функция без аргументов, а не само значение:

defaultdict(list) # [] defaultdict(int) # 0 defaultdict(set) # set() defaultdict(lambda: 'нет') # своё значение

Главная ловушка

Обращение к ключу создаёт его. Даже простое чтение:

d = defaultdict(int) print(d['новый']) # 0 print(d) # defaultdict(<class 'int'>, {'новый': 0}) — ключ появился! print(len(d)) # 1

Если это нежелательно, используйте .get() или обычный словарь. На больших циклах такое «случайное» создание ключей может незаметно съесть память.

Вложенные структуры

# двумерный словарь matrix = defaultdict(lambda: defaultdict(int)) matrix['a']['b'] += 1 # группировка words = ['кот', 'ток', 'дом', 'мод'] groups = defaultdict(list) for word in words: groups[''.join(sorted(word))].append(word) print(dict(groups)) # {'кот': ['кот', 'ток'], 'дмо': ['дом', 'мод']}

Последний пример — готовое решение задачи о группировке анаграмм.

deque: очередь с двух концов

Список в Python медленно работает с началом: pop(0) и insert(0, x) стоят O(n). deque даёт O(1) с обоих концов.

from collections import deque d = deque([1, 2, 3]) d.append(4) # справа d.appendleft(0) # слева d.pop() # справа d.popleft() # слева, O(1) — в отличие от list.pop(0)

Обязателен для BFS:

def bfs(graph: dict, start): visited = {start} queue = deque([start]) while queue: node = queue.popleft() # со списком было бы O(n) for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return visited

Полезный параметр maxlen — автоматическое окно последних N элементов:

last_five = deque(maxlen=5) for i in range(10): last_five.append(i) print(last_five) # deque([5, 6, 7, 8, 9], maxlen=5)

namedtuple: кортеж с именами

Превращает безымянные индексы в читаемые поля.

from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(3, 4) print(p.x, p.y) # 3 4 print(p[0]) # 3 — работает и как обычный кортеж # распаковка x, y = p

Занимает столько же памяти, сколько обычный кортеж, но код становится понятнее: p.x вместо p[0].

Полезные методы:

p._replace(x=10) # новый объект (кортежи неизменяемы) p._asdict() # {'x': 3, 'y': 4} Point._fields # ('x', 'y')

В современном коде часто предпочитают dataclass или typing.NamedTuple — они поддерживают аннотации типов:

from typing import NamedTuple class Point(NamedTuple): x: int y: int

OrderedDict: когда обычного словаря мало

С Python 3.7 обычные словари сохраняют порядок вставки, поэтому OrderedDict нужен реже. Но у него остались уникальные возможности.

from collections import OrderedDict od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) od.move_to_end('a') # переместить в конец od.move_to_end('c', last=False) # переместить в начало od.popitem(last=False) # удалить первый (FIFO)

Это делает OrderedDict готовой основой для LRU-кэша:

class LRUCache: def __init__(self, capacity: int) -> None: self.capacity = capacity self.cache = OrderedDict() def get(self, key): if key not in self.cache: return -1 self.cache.move_to_end(key) # освежаем return self.cache[key] def put(self, key, value) -> None: if key in self.cache: self.cache.move_to_end(key) elif len(self.cache) == self.capacity: self.cache.popitem(last=False) # удаляем самый давний self.cache[key] = value

Ещё отличие: OrderedDict учитывает порядок при сравнении, обычный словарь — нет.

OrderedDict(a=1, b=2) == OrderedDict(b=2, a=1) # False dict(a=1, b=2) == dict(b=2, a=1) # True

ChainMap: словари без копирования

Объединяет несколько словарей в один вид без создания копии.

from collections import ChainMap defaults = {'theme': 'light', 'lang': 'ru'} user = {'theme': 'dark'} config = ChainMap(user, defaults) print(config['theme']) # dark — первый словарь важнее print(config['lang']) # ru — взято из defaults

Отличие от {**defaults, **user} — данные не копируются. Изменения в исходных словарях сразу видны через ChainMap, а память не удваивается. Удобно для многоуровневых конфигураций: аргументы командной строки, переменные окружения, файл настроек, значения по умолчанию.

Что чем заменить

ЗадачаПлохоХорошо
Подсчёт элементовручной словарь с проверкамиCounter
Группировкаif key not in d: d[key] = []defaultdict(list)
Очередьlist.pop(0)deque.popleft()
Точка, записькортеж с индексамиnamedtuple
LRU-кэшручной списокOrderedDict
Слияние настроек{**a, **b}ChainMap

Частые ошибки

defaultdict создаёт ключи при чтении. Проверяйте наличие через in или .get(), если это критично.

Передача значения вместо фабрики. defaultdict([]) вызовет ошибку — нужен defaultdict(list), без скобок у аргумента.

Counter с нечисловыми значениями. Арифметика работает только с числами; попытка сложить счётчики строк даст неожиданный результат.

Использование списка как очереди. Самая дорогая ошибка: скрытый квадрат вместо линии.

Одна изменяемая фабрика на всех. defaultdict(lambda: some_list) вернёт один и тот же список для всех ключей. Нужна фабрика, создающая новый объект: defaultdict(list).

Что запомнить

  • Counter считает элементы и поддерживает арифметику множеств.
  • defaultdict избавляет от проверок наличия ключа, но создаёт ключи при чтении.
  • deque — единственный правильный выбор для очереди в Python.
  • namedtuple делает кортежи читаемыми без затрат памяти.
  • OrderedDict нужен ради move_to_end и popitem(last=False) — основа LRU.
  • ChainMap объединяет словари без копирования данных.

Задачи по теме

Самые часто встречающиеся элементы

Массивы и Хеширование
Средне

Дан массив целых чисел nums и число k. Верните k наиболее часто встречающихся элементов массива. Порядок элементов в ответе не важен.

#Массивы#Хеш-таблицы
Продуктовые компанииСовременные задачиИнтенсивная подготовка
20 мин

Группировка анаграмм

Массивы и Хеширование
Средне

Дан массив строк strs. Сгруппируйте все анаграммы вместе в подсписки. Анаграмма — это строка, которая содержит те же символы, что и другая строка, но в другом порядке.

#Массивы#Хеш-таблицы
Стандартные собеседованияПродуктовые компанииИнтенсивная подготовка
20 мин

Дубликаты в массиве

Массивы и Хеширование
Легко

Найдите, содержит ли массив какие-либо дубликаты. Функция должна вернуть true, если какое-либо значение появляется минимум дважды, и false, если каждый элемент уникален.

#Массивы#Хеш-таблицы
Базовые алгоритмыСтартапы и финтехУниверсальный набор
15 мин