Модуль collections: Counter, defaultdict, deque
Counter — подсчёт
Counter — словарь «элемент → количество».
from collections import Counter
c = Counter("абракадабра")
print(c)
print(c["а"], c["я"]) # отсутствующий элемент — 0, а не KeyError
print(c.most_common(2))
words = "to be or not to be that is".split()
wc = Counter(words)
wc.update(["be", "be"]) # добавить ещё
print(wc.most_common(3))
print(sum(wc.values()), len(wc)) # всего слов, уникальных
Counter поддерживает арифметику:
from collections import Counter
inventory = Counter(apples=5, pears=2)
sold = Counter(apples=3, pears=2)
print(inventory - sold) # отрицательные и нулевые отбрасываются
print(inventory + sold, inventory & sold, inventory | sold)
print(list((inventory + sold).elements()))
defaultdict — значение по умолчанию
defaultdict(фабрика) при обращении к отсутствующему ключу вызывает фабрику и вставляет результат:
from collections import defaultdict
groups = defaultdict(list)
for name, dept in [("Анна", "IT"), ("Борис", "HR"), ("Вера", "IT")]:
groups[dept].append(name) # без проверки «есть ли ключ»
print(dict(groups))
counts = defaultdict(int) # int() == 0
for ch in "hello":
counts[ch] += 1
print(dict(counts))
graph = defaultdict(set)
for a, b in [(1, 2), (1, 3), (2, 3)]:
graph[a].add(b)
graph[b].add(a)
print(dict(graph))
Чтение тоже создаёт ключ
groups["нет такого"] не бросит ошибку, а добавит пустой список. Для проверки используйте "ключ" in groups или groups.get("ключ").
deque — двусторонняя очередь
deque (произносится «дек») — быстрые O(1) операции с обоих концов.
from collections import deque
q = deque([1, 2, 3])
q.append(4) # в конец
q.appendleft(0) # в начало
print(q.pop(), q.popleft(), q)
q.rotate(1) # циклический сдвиг вправо
print(q)
recent = deque(maxlen=3) # хранит только последние 3
for page in ["/", "/about", "/blog", "/contacts"]:
recent.append(page)
print(recent)
Очередь задач (FIFO) и обход графа в ширину (BFS) пишут именно на deque:
from collections import deque
def bfs_order(graph, start):
seen, order, queue = {start}, [], deque([start])
while queue:
node = queue.popleft()
order.append(node)
for nxt in graph[node]:
if nxt not in seen:
seen.add(nxt)
queue.append(nxt)
return order
g = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}
print(bfs_order(g, "A"))
| Операция | list | deque |
|---|---|---|
| добавить / удалить в конце | O(1) | O(1) |
| добавить / удалить в начале | O(n) | O(1) |
доступ по индексу x[i] |
O(1) | O(n) в середине |
namedtuple
Кортеж с именованными полями (подробнее — в уроке «Кортежи»):
from collections import namedtuple
Point = namedtuple("Point", "x y")
p = Point(1, 2)
print(p.x + p.y, p._replace(x=10), Point._fields)
OrderedDict
Обычный dict уже хранит порядок, но у OrderedDict есть move_to_end и popitem(last=False) — готовая основа для LRU-кеша (частая задача на собеседовании):
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return -1
self.data.move_to_end(key) # недавно использованный — в конец
return self.data[key]
def put(self, key, value):
self.data[key] = value
self.data.move_to_end(key)
if len(self.data) > self.capacity:
self.data.popitem(last=False) # вытеснить самый старый
cache = LRUCache(2)
cache.put("a", 1); cache.put("b", 2)
cache.get("a") # теперь "b" — самый старый
cache.put("c", 3) # вытесняет "b"
print(list(cache.data.items()), cache.get("b"))
ChainMap — слои словарей
Ищет ключ по очереди в нескольких словарях — удобно для настроек «аргументы командной строки → переменные окружения → значения по умолчанию»:
from collections import ChainMap
defaults = {"debug": False, "port": 8000}
env = {"port": 9000}
cli = {"debug": True}
config = ChainMap(cli, env, defaults)
print(config["debug"], config["port"], dict(config))
Итог
Counter— подсчёт,most_common, арифметика.defaultdict(list/int/set)— группировка и подсчёт без проверок ключа.deque— очередь с O(1) с обоих концов,maxlenдля «последних N»; BFS.namedtuple— кортеж с полями;OrderedDict—move_to_endдля LRU;ChainMap— слои настроек.
Практика решено 0 из 5
Напишите функцию top_words(text, n), которая возвращает список из n самых частых слов
(в нижнем регистре) в виде пар (слово, количество). Используйте Counter.most_common.
Какие тесты проверяют код (2)
assert top_words("a b a c b a", 2) == [("a", 3), ("b", 2)]
assert top_words("Кот кот пёс", 1) == [("кот", 2)]
from collections import Counter
def top_words(text, n):
return Counter(text.lower().split()).most_common(n)
Напишите функцию is_anagram(a, b): проверить, что строки состоят из одних и тех же букв
с учётом количества (регистр и пробелы не учитывать).
Какие тесты проверяют код (3)
assert is_anagram("Listen", "Silent") is True
assert is_anagram("aab", "abb") is False
assert is_anagram("кабан", "банка") is True
from collections import Counter
def is_anagram(a, b):
clean = lambda s: s.replace(" ", "").lower()
return Counter(clean(a)) == Counter(clean(b))
Напишите функцию group_by_first_letter(words), которая возвращает defaultdict(list)
«первая буква → список слов».
Какие тесты проверяют код (2)
r = group_by_first_letter(["арбуз", "банан", "абрикос"]); assert r == {"а": ["арбуз", "абрикос"], "б": ["банан"]}
assert isinstance(group_by_first_letter([]), defaultdict)
from collections import defaultdict
def group_by_first_letter(words):
groups = defaultdict(list)
for w in words:
groups[w[0]].append(w)
return groups
Напишите функцию moving_average(nums, k), которая возвращает список средних значений
каждого окна из k подряд идущих чисел. Используйте deque(maxlen=k) и текущую сумму окна.
Какие тесты проверяют код (3)
assert moving_average([1, 2, 3, 4, 5], 2) == [1.5, 2.5, 3.5, 4.5] assert moving_average([1, 2, 3], 3) == [2.0] assert moving_average([1, 2], 3) == []
from collections import deque
def moving_average(nums, k):
window = deque(maxlen=k)
total = 0
result = []
for x in nums:
if len(window) == k:
total -= window[0]
window.append(x)
total += x
if len(window) == k:
result.append(total / k)
return result
Почему для очереди (добавить в конец, забрать из начала) используют deque, а не list?
Войдите, чтобы отмечать пройденные уроки.