Модуль 6 · Урок 6.4
Стандартная библиотека и перебор комбинаций
Counter, defaultdict, deque, точные Fraction и Decimal, math и itertools: product, permutations, combinations.
Сосчитать буквы слова "BANANA", обслужить заявки по очереди, сложить 1/3 и 1/6 без погрешности, перебрать все слова из букв A, B, C — всё это можно написать циклами и словарями. Но такие задачи встречаются так часто, что для них есть готовые средства. Стандартная библиотека Python — набор модулей, которые есть в любой установке; ими пользуются, когда понятно, что они делают внутри.
Разберём подсчёт, очередь, точные дроби и перебор комбинаций — на тех же примерах.
Обзор библиотеки
Каждое средство библиотеки делает то, что ты уже умеешь писать сам. Модуль подключают через import, как math в уроке о модулях. Средства этого и прошлых уроков:
| Задача | Средство | Где |
|---|---|---|
| Частоты значений | collections.Counter | этот урок |
| Словарь со значением по умолчанию | collections.defaultdict | этот урок |
| Очередь | collections.deque | этот урок |
| Точные дроби | fractions, decimal | этот урок |
| Перебор слов и комбинаций | itertools | этот урок |
| Запомнить результаты функции | functools.cache | урок о рекурсии |
| Пути, CSV, JSON | pathlib, csv, json | урок о файлах |
Перед каждым средством стоит один вопрос: что оно делает внутри и при каких условиях ответ верен. cache требует, чтобы аргументы полностью определяли результат, а для itertools надо заранее понимать, сколько вариантов получится.
Counter и defaultdict
Counter — готовый словарь частот, defaultdict сам создаёт значение для нового ключа.
from collections import Counter, defaultdict counts = Counter("BANANA")print(counts["A"])print(counts["Z"])print(counts.most_common(2)) groups = defaultdict(list)groups["python"].append("Анна")print(groups["python"])Counter делает то же, что цикл counts[x] = counts.get(x, 0) + 1 из урока о словарях, а для отсутствующего ключа возвращает 0 без ошибки. most_common(k) даёт k самых частых пар «значение — частота». defaultdict(list) получает функцию list без скобок и вызывает её для каждого нового ключа — так у ключа сразу появляется пустой список.
deque
deque — очередь: добавляют в конец, быстро забирают из начала. Очередь работает по правилу «первым пришёл — первым вышел», так обслуживают заявки по порядку поступления.
from collections import deque queue = deque([1, 2, 3])queue.append(4)first = queue.popleft()print(first, list(queue))append добавляет справа, popleft удаляет и возвращает элемент слева. У списка то же делает pop(0), но медленно: элементы списка стоят по номерам, и после удаления первого все остальные сдвигаются на место левее.
pop(0) сдвигает почти миллион элементов на каждой заявке, popleft — ни одного.Fraction и Decimal
Fraction и Decimal считают точно там, где float ошибается. В уроке о числах 0.1 + 0.2 дало не ровно 0.3. Точные числа решают это:
from fractions import Fractionfrom decimal import Decimalfrom math import isclose print(Fraction(1, 3) + Fraction(1, 6))print(Decimal("0.1") + Decimal("0.2"))print(isclose(0.1 + 0.2, 0.3))Fraction хранит числитель и знаменатель, поэтому 1/3 + 1/6 даёт ровно 1/2. Decimal создают из строки: Decimal(0.1) перенёс бы уже неточное float. Если точность не нужна, а нужно сравнить два float, isclose(a, b) проверяет, что они почти равны.
Тонкости:точность Decimal и сравнение с нулём
У Decimal настраиваемая точность: деление 1 на 3 не станет конечной десятичной дробью. У isclose допуск по умолчанию относительный, и около нуля он не срабатывает — нужен явный abs_tol: isclose(value, 0.0, abs_tol=1e-9). Запись 1e-9 — это 10 в степени −9.
product и перебор
product перебирает все слова заданной длины, и перебор надёжен, только если набор полный. Полный перебор рассматривает все допустимые варианты и проверяет нужное свойство. Перед кодом ответь: из чего состоит один вариант, как получить все без пропусков и сколько их будет.
itertools.product выдаёт слова по порядку, как счётчик пробега: правая позиция перебирает все буквы, затем левая сдвигается на следующую, а правая начинает сначала. Пройди по шагам слова длины 2 из букв A, B, C:
- Первое слово AA: на обеих позициях первая буква.
- AB: правая позиция сдвинулась A → B.
- AC: правая позиция сдвинулась B → C.
- BA: правая позиция прошла все буквы и начала сначала, левая сдвинулась A → B — как счётчик пробега.
- BB: правая позиция сдвинулась A → B.
- BC: правая позиция сдвинулась B → C.
- CA: правая позиция прошла все буквы и начала сначала, левая сдвинулась B → C — как счётчик пробега.
- CB: правая позиция сдвинулась A → B.
- CC: правая позиция сдвинулась B → C. Все 3 · 3 = 9 слов — без пропусков и повторов.
Посчитаем слова длины 3 из букв A, B, C, в которых ровно одна A:
from itertools import product count = 0for letters in product("ABC", repeat=3): word = "".join(letters) if word.count("A") == 1: count += 1print(count)product выдаёт кортежи букв, например ('A', 'B', 'C'); "".join склеивает их в слово. Всего слов 3 ** 3 = 27. Подходящих 12: одна из трёх позиций под A, на двух других — по два варианта, 3 · 2 · 2 = 12. Такой ручной подсчёт — независимая проверка программы.
Перестановки и сочетания
Перестановки, сочетания и слова различаются тем, важен ли порядок и можно ли повторять. product строит слова, permutations — расстановки разных предметов, combinations — выбор группы, combinations_with_replacement — выбор с повторениями без порядка. Разницу видно на парах из букв A, B, C:
product берёт все 9 клеток. permutations убирает диагональ — повтор буквы. combinations оставляет одну пару из AB и BA — порядок не важен. С _with_replacement диагональ возвращается.from itertools import combinations, permutations, product items = "ABC"print(len(list(product(items, repeat=2))))print(len(list(permutations(items, 2))))print(len(list(combinations(items, 2))))print(["".join(p) for p in combinations(items, 2)])Числа получаются выбором по позициям: 3 · 3 = 9; 3 · 2 = 6 — вторая буква не может повторить первую; 6 / 2 = 3 — пары AB и BA становятся одной группой.
Функции различают элементы по позиции в источнике. Если в слове есть одинаковые буквы, перестановки дадут одинаковые слова, и их убирают через set(...). Если вариант — запись числа, первая цифра обычно не может быть нулём: это условие добавляют отдельно. И не собирай все варианты в список, если их можно проверять по одному.
Итог
Готовое средство заменяет цикл, который ты умеешь писать сам, — если понятно, что оно делает и сколько работы выполнит. Вот что понадобится дальше.
CounterСловарь частот;most_common(k)— самые частые;defaultdictсоздаёт значение нового ключа.dequeappendв конец,popleftиз начала без сдвига остальных.- Точные числа
Fraction— дроби,Decimal— из строки;iscloseсравниваетfloat. itertoolsproduct— слова с повторами,permutations— порядок без повторов,combinations— группы.- ПереборСначала считают число вариантов и проверяют, что набор полный.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Сколько слов выдаст product("AB", repeat=4) и какое из них будет третьим?
ОтветСкрыть ответ
2 · 2 · 2 · 2 = 16; по порядку счётчика — AAAA, AAAB, AABA, значит третье — AABA.
Что вернёт Counter("MISSISSIPPI").most_common(2)?
ОтветСкрыть ответ
[('I', 4), ('S', 4)]: при равных частотах порядок — как буквы впервые встретились в слове.
Сгруппируй слова ["кот", "дом", "кит"] по первой букве через defaultdict(list). Что получится?
ОтветСкрыть ответ
{'к': ['кот', 'кит'], 'д': ['дом']}.
Начни с q = deque([5, 6]), выполни q.append(7), q.popleft(), q.popleft(). Что осталось в очереди?
ОтветСкрыть ответ
Одно число 7: popleft забрал 5 и 6.
Что напечатает print(Fraction(1, 2) + Fraction(1, 3))?
ОтветСкрыть ответ
5/6: 3/6 + 2/6.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Напиши программу: две единицы из четырёх
Напиши программу: она перебирает через product("01", repeat=4) все двоичные строки длины 4 и считает те, в которых ровно две единицы. Запусти её и запиши, что она напечатала.
Введите число.
Подсказка
Склей кортеж в строку через "".join и проверь count("1") == 2.
Разбор
Например:
from itertools import product count = 0for digits in product("01", repeat=4): if "".join(digits).count("1") == 2: count += 1print(count)Две позиции единиц из четырёх: 0011, 0101, 0110, 1001, 1010, 1100.
Ответ: 6
Что напечатает программа?
from fractions import Fractionfrom decimal import Decimal print(Fraction(1, 4) + Fraction(1, 6))print(Decimal("0.1") * 3)Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Общий знаменатель 12; Decimal из строки считает десятичные дроби без погрешности.
Разбор
1/4 + 1/6 = 3/12 + 2/12 = 5/12; 0.1 · 3 = 0.3 точно.
Ответ: 5/12 0.3
Что напечатает программа?
from itertools import combinations, permutations, product print(len(list(product("AB", repeat=3))))print(len(list(permutations("ABCD", 2))))print(len(list(combinations("ABCD", 2))))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Слова — 2 · 2 · 2; пары разных букв с порядком — 4 · 3; группы без порядка — вдвое меньше.
Разбор
product: 2³ = 8; permutations: 4 · 3 = 12; combinations: 12 / 2 = 6.
Ответ: 8 12 6
Напиши программу: без двух единиц подряд
Напиши программу: она перебирает все двоичные строки длины 4 и считает те, в которых нет двух единиц подряд. Запусти её и запиши, что она напечатала.
Введите число.
Подсказка
Строка подходит, если в ней нет подстроки "11".
Разбор
Например:
from itertools import product count = 0for digits in product("01", repeat=4): if "11" not in "".join(digits): count += 1print(count)Подходят 0000, 0001, 0010, 0100, 0101, 1000, 1001, 1010.
Ответ: 8
Напиши программу: слова из букв «ПАПА»
Напиши программу: собери через permutations все перестановки букв слова «ПАПА», убери повторы через set и напечатай число различных слов. Запусти её и запиши, что она напечатала.
Введите число.
Подсказка
Одинаковые буквы дают одинаковые слова: считай len(set(...)).
Разбор
Например:
from itertools import permutations words = {"".join(p) for p in permutations("ПАПА")}print(len(words))4! = 24 перестановки, но каждое слово повторяется 2 · 2 = 4 раза: 24 / 4 = 6.
Ответ: 6