К содержанию

Модуль 6 · Урок 6.4

Стандартная библиотека и перебор комбинаций

Counter, defaultdict, deque, точные Fraction и Decimal, math и itertools: product, permutations, combinations.

5 задач

Сосчитать буквы слова "BANANA", обслужить заявки по очереди, сложить 1/3 и 1/6 без погрешности, перебрать все слова из букв A, B, C — всё это можно написать циклами и словарями. Но такие задачи встречаются так часто, что для них есть готовые средства. Стандартная библиотека Python — набор модулей, которые есть в любой установке; ими пользуются, когда понятно, что они делают внутри.

Разберём подсчёт, очередь, точные дроби и перебор комбинаций — на тех же примерах.

Обзор библиотеки

Каждое средство библиотеки делает то, что ты уже умеешь писать сам. Модуль подключают через import, как math в уроке о модулях. Средства этого и прошлых уроков:

ЗадачаСредствоГде
Частоты значенийcollections.Counterэтот урок
Словарь со значением по умолчаниюcollections.defaultdictэтот урок
Очередьcollections.dequeэтот урок
Точные дробиfractions, decimalэтот урок
Перебор слов и комбинацийitertoolsэтот урок
Запомнить результаты функцииfunctools.cacheурок о рекурсии
Пути, CSV, JSONpathlib, 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"])
3 0 [('A', 3), ('N', 2)] ['Анна']

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))
1 [2, 3, 4]

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))
1/2 0.3 True

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:

  1. Первое слово AA: на обеих позициях первая буква.
  2. AB: правая позиция сдвинулась A → B.
  3. AC: правая позиция сдвинулась B → C.
  4. BA: правая позиция прошла все буквы и начала сначала, левая сдвинулась A → B — как счётчик пробега.
  5. BB: правая позиция сдвинулась A → B.
  6. BC: правая позиция сдвинулась B → C.
  7. CA: правая позиция прошла все буквы и начала сначала, левая сдвинулась B → C — как счётчик пробега.
  8. CB: правая позиция сдвинулась A → B.
  9. 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)
12

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)])
9 6 3 ['AB', 'AC', 'BC']

Числа получаются выбором по позициям: 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 — группы.
  • ПереборСначала считают число вариантов и проверяют, что набор полный.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Сколько слов выдаст product("AB", repeat=4) и какое из них будет третьим?

ОтветСкрыть ответ
Ответ

2 · 2 · 2 · 2 = 16; по порядку счётчика — AAAA, AAAB, AABA, значит третье — AABA.

Вопрос 2

Что вернёт Counter("MISSISSIPPI").most_common(2)?

ОтветСкрыть ответ
Ответ

[('I', 4), ('S', 4)]: при равных частотах порядок — как буквы впервые встретились в слове.

Вопрос 3

Сгруппируй слова ["кот", "дом", "кит"] по первой букве через defaultdict(list). Что получится?

ОтветСкрыть ответ
Ответ

{'к': ['кот', 'кит'], 'д': ['дом']}.

Вопрос 4

Начни с q = deque([5, 6]), выполни q.append(7), q.popleft(), q.popleft(). Что осталось в очереди?

ОтветСкрыть ответ
Ответ

Одно число 7: popleft забрал 5 и 6.

Вопрос 5

Что напечатает print(Fraction(1, 2) + Fraction(1, 3))?

ОтветСкрыть ответ
Ответ

5/6: 3/6 + 2/6.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Напиши программу: две единицы из четырёх

Напиши программу: она перебирает через product("01", repeat=4) все двоичные строки длины 4 и считает те, в которых ровно две единицы. Запусти её и запиши, что она напечатала.

Введите число.

Что напечатает программа?

from fractions import Fractionfrom decimal import Decimal print(Fraction(1, 4) + Fraction(1, 6))print(Decimal("0.1") * 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))))

Запишите напечатанные строки через пробел в порядке вывода.

Напиши программу: без двух единиц подряд

Напиши программу: она перебирает все двоичные строки длины 4 и считает те, в которых нет двух единиц подряд. Запусти её и запиши, что она напечатала.

Введите число.

Напиши программу: слова из букв «ПАПА»

Напиши программу: собери через permutations все перестановки букв слова «ПАПА», убери повторы через set и напечатай число различных слов. Запусти её и запиши, что она напечатала.

Введите число.