Python · Синтаксис · Промежуточный

itertools

10 задач

Освойте модуль itertools в Python: инструменты для работы с итераторами, генерации последовательностей и эффективного решения комбинаторных задач.

itertools: бесконечные итераторы и срезы

#
Модуль `itertools` предоставляет памятеэффективные инструменты для работы с последовательностями и итераторами. Все функции возвращают **итераторы** — они генерируют значения по требованию, не строя полные списки в памяти. ```python import itertools ``` ## Цепочка итерируемых ```python # chain: объединить несколько итерируемых в одну последовательность result = list(itertools.chain([1, 2], [3, 4], [5])) print(result) # [1, 2, 3, 4, 5] # chain.from_iterable: выровнять на один уровень nested = [[1, 2], [3, 4], [5, 6]] flat = list(itertools.chain.from_iterable(nested)) print(flat) # [1, 2, 3, 4, 5, 6] ``` ## islice: срезы любого итератора `islice` позволяет нарезать итераторы, которые не поддерживают `[start:stop]`: ```python # islice(iterable, stop) # islice(iterable, start, stop) # islice(iterable, start, stop, step) data = range(100) first_five = list(itertools.islice(data, 5)) print(first_five) # [0, 1, 2, 3, 4] skip_then_take = list(itertools.islice(data, 10, 15)) print(skip_then_take) # [10, 11, 12, 13, 14] ``` ## Бесконечные итераторы Никогда не останавливаются — всегда используйте с `islice` или условием `break`: ```python # count(start=0, step=1): считает бесконечно counter = itertools.count(10, 2) print(list(itertools.islice(counter, 5))) # [10, 12, 14, 16, 18] # cycle(iterable): бесконечно повторяет последовательность colors = itertools.cycle(["red", "green", "blue"]) print(list(itertools.islice(colors, 7))) # ["red", "green", "blue", "red", "green", "blue", "red"] # repeat(object, times=None): повторить одно значение fives = itertools.repeat(5, 4) print(list(fives)) # [5, 5, 5, 5] # repeat без times — бесконечный, удобен с map/zip result = list(map(lambda x, n: x ** n, [1, 2, 3, 4], itertools.repeat(2))) print(result) # [1, 4, 9, 16] ``` ## zip_longest: zip для неравных последовательностей ```python a = [1, 2, 3] b = ["a", "b"] # Встроенный zip останавливается на более короткой: print(list(zip(a, b))) # [(1, "a"), (2, "b")] # zip_longest заполняет пропущенные значения: print(list(itertools.zip_longest(a, b, fillvalue=0))) # [(1, "a"), (2, "b"), (3, 0)] ``` ## accumulate: накопленные суммы ```python import operator data = [1, 2, 3, 4, 5] # Накопленная сумма (по умолчанию) print(list(itertools.accumulate(data))) # [1, 3, 6, 10, 15] # Накопленное произведение print(list(itertools.accumulate(data, operator.mul))) # [1, 2, 6, 24, 120] # Накопленный максимум print(list(itertools.accumulate([3, 1, 4, 1, 5, 9, 2, 6], max))) # [3, 3, 4, 4, 5, 9, 9, 9] ``` ## takewhile и dropwhile ```python data = [1, 3, 5, 2, 4, 6] # takewhile: брать элементы пока условие True, затем остановиться print(list(itertools.takewhile(lambda x: x % 2 != 0, data))) # [1, 3, 5] — останавливается на 2 (первое чётное число) # dropwhile: пропускать элементы пока условие True, затем возвращать остальные print(list(itertools.dropwhile(lambda x: x % 2 != 0, data))) # [2, 4, 6] — пропускает нечётные вначале, возвращает с первого чётного ```

Комбинаторика с itertools

#
## product: декартово произведение ```python import itertools # Все комбинации размеров и цветов sizes = ["S", "M", "L"] colors = ["red", "blue"] variants = list(itertools.product(sizes, colors)) print(variants) # [("S","red"), ("S","blue"), ("M","red"), ("M","blue"), ("L","red"), ("L","blue")] # product с repeat: бросок 2 кубиков dice = list(itertools.product(range(1, 7), repeat=2)) print(len(dice)) # 36 print(dice[:3]) # [(1, 1), (1, 2), (1, 3)] ``` ## permutations: упорядоченные расстановки ```python # permutations(iterable, r=None): все упорядоченные расстановки длины r letters = ["A", "B", "C"] # Все перестановки длины 3 (r по умолчанию len) perms = list(itertools.permutations(letters)) print(len(perms)) # 6 (3! = 6) print(perms) # [("A","B","C"), ("A","C","B"), ("B","A","C"), ...] # Перестановки длины 2 perms2 = list(itertools.permutations(letters, 2)) print(perms2) # [("A","B"), ("A","C"), ("B","A"), ("B","C"), ("C","A"), ("C","B")] ``` ## combinations: неупорядоченные выборки ```python # combinations(iterable, r): длина r, без повторений, порядок не важен people = ["Alice", "Bob", "Carol", "David"] pairs = list(itertools.combinations(people, 2)) print(len(pairs)) # 6 (C(4,2) = 6) print(pairs[:3]) # [("Alice","Bob"), ("Alice","Carol"), ("Alice","David")] # combinations_with_replacement: допускает повторения dice_pairs = list(itertools.combinations_with_replacement(range(1, 4), 2)) print(dice_pairs) # [(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)] ``` ## groupby: группировка последовательных элементов `groupby` группирует **последовательные** элементы с одинаковым ключом. Входные данные должны быть отсортированы по ключу! ```python data = [ {"name": "Alice", "dept": "Engineering"}, {"name": "Bob", "dept": "Engineering"}, {"name": "Carol", "dept": "Marketing"}, {"name": "David", "dept": "Marketing"}, {"name": "Eve", "dept": "Engineering"}, ] # Сначала сортируем по ключу группировки data.sort(key=lambda x: x["dept"]) for dept, members in itertools.groupby(data, key=lambda x: x["dept"]): names = [m["name"] for m in members] print(f"{dept}: {names}") # Engineering: ["Alice", "Bob", "Eve"] # Marketing: ["Carol", "David"] ``` ## filterfalse: противоположное filter ```python nums = [1, 2, 3, 4, 5, 6, 7, 8] # filter: оставить элементы где условие True evens = list(filter(lambda x: x % 2 == 0, nums)) print(evens) # [2, 4, 6, 8] # filterfalse: оставить элементы где условие False odds = list(itertools.filterfalse(lambda x: x % 2 == 0, nums)) print(odds) # [1, 3, 5, 7] ```

Справочник: itertools

#
## Бесконечные итераторы | Функция | Сигнатура | Что делает | |---|---|---| | `count` | `count(start=0, step=1)` | Считает от start бесконечно | | `cycle` | `cycle(iterable)` | Повторяет iterable бесконечно | | `repeat` | `repeat(obj, times=None)` | Повторяет obj, опционально N раз | ## Срезание итераторов | Функция | Сигнатура | Что делает | |---|---|---| | `islice` | `islice(it, stop)` или `islice(it, start, stop[, step])` | Срезать любой итератор | | `takewhile` | `takewhile(pred, it)` | Возвращать пока pred True | | `dropwhile` | `dropwhile(pred, it)` | Пропускать пока pred True, затем остальное | | `filterfalse` | `filterfalse(pred, it)` | Возвращать где pred False | ## Объединение итераторов | Функция | Сигнатура | Что делает | |---|---|---| | `chain` | `chain(*iterables)` | Конкатенация итерируемых | | `chain.from_iterable` | `chain.from_iterable(iterable)` | Выровнять на один уровень | | `zip_longest` | `zip_longest(*its, fillvalue=None)` | Zip с заполнением коротких | | `accumulate` | `accumulate(it, func=operator.add)` | Накопленный итог | | `groupby` | `groupby(it, key=None)` | Группировка последовательных элементов | ## Комбинаторика | Функция | Сигнатура | Количество | |---|---|---| | `product` | `product(*its, repeat=1)` | n1 × n2 × ... | | `permutations` | `permutations(it, r=None)` | n! / (n-r)! | | `combinations` | `combinations(it, r)` | n! / (r! × (n-r)!) | | `combinations_with_replacement` | `combinations_with_replacement(it, r)` | (n+r-1)! / (r! × (n-1)!) | ## Подводный камень groupby ```python # НЕПРАВИЛЬНО: groupby группирует только ПОСЛЕДОВАТЕЛЬНЫЕ равные элементы data = [1, 2, 1, 2] for k, g in itertools.groupby(data): print(k, list(g)) # 1 [1] # 2 [2] # 1 [1] ← НЕ сгруппировано с первым 1! # 2 [2] # ПРАВИЛЬНО: сначала отсортировать data.sort() for k, g in itertools.groupby(data): print(k, list(g)) # 1 [1, 1] # 2 [2, 2] ``` ## Распространённые паттерны ```python import itertools, operator # Выровнять вложенный список flat = list(itertools.chain.from_iterable(nested)) # Накопленная сумма running = list(itertools.accumulate(values)) # Первые N элементов генератора head = list(itertools.islice(gen, N)) # Все пары (без самопар) pairs = list(itertools.combinations(items, 2)) ```
01

Выровнять вложенный список

#

Напишите функцию `flatten(nested)`, которая принимает список списков и возвращает плоский список с помощью `itertools.chain.from_iterable`. Пример: `flatten([[1, 2], [3], [4, 5, 6]])` → `[1, 2, 3, 4, 5, 6]`.

from itertools import chain


def flatten(nested):
    pass


print(flatten([[1, 2], [3], [4, 5, 6]]))
# [1, 2, 3, 4, 5, 6]
Решение
from itertools import chain


def flatten(nested):
    return list(chain.from_iterable(nested))


print(flatten([[1, 2], [3], [4, 5, 6]]))
# [1, 2, 3, 4, 5, 6]
02

Первые N из генератора

#

Напишите функцию `first_n(gen, n)`, которая возвращает первые `n` элементов из любого генератора или итератора в виде списка, используя `itertools.islice`. Пример: `first_n((x**2 for x in range(100)), 5)` → `[0, 1, 4, 9, 16]`.

from itertools import islice


def first_n(gen, n):
    pass


print(first_n((x**2 for x in range(100)), 5))
# [0, 1, 4, 9, 16]
Решение
from itertools import islice


def first_n(gen, n):
    return list(islice(gen, n))


print(first_n((x**2 for x in range(100)), 5))
# [0, 1, 4, 9, 16]
03

Планировщик round-robin

#

Напишите функцию `round_robin(tasks)`, которая принимает список имён задач и возвращает бесконечный цикличный итератор с помощью `itertools.cycle`. Затем используйте `itertools.islice` для получения первых 8 назначений. Пример: `list(islice(round_robin(["A", "B", "C"]), 8))` → `["A", "B", "C", "A", "B", "C", "A", "B"]`.

from itertools import cycle, islice


def round_robin(tasks):
    pass


print(list(islice(round_robin(["A", "B", "C"]), 8)))
# ["A", "B", "C", "A", "B", "C", "A", "B"]
Решение
from itertools import cycle, islice


def round_robin(tasks):
    return cycle(tasks)


print(list(islice(round_robin(["A", "B", "C"]), 8)))
# ["A", "B", "C", "A", "B", "C", "A", "B"]
04

Слить неравные последовательности

#

Напишите функцию `merge_sequences(seq1, seq2, seq3, fill=None)`, которая объединяет три последовательности поэлементно с помощью `itertools.zip_longest`, используя `fill` как недостающее значение. Возвращает результат как список кортежей. Пример: `merge_sequences([1,2,3], ["a","b"], [True], fill=0)` → `[(1,"a",True), (2,"b",0), (3,0,0)]`.

from itertools import zip_longest


def merge_sequences(seq1, seq2, seq3, fill=None):
    pass


print(merge_sequences([1, 2, 3], ["a", "b"], [True], fill=0))
# [(1, "a", True), (2, "b", 0), (3, 0, 0)]
Решение
from itertools import zip_longest


def merge_sequences(seq1, seq2, seq3, fill=None):
    return list(zip_longest(seq1, seq2, seq3, fillvalue=fill))


print(merge_sequences([1, 2, 3], ["a", "b"], [True], fill=0))
# [(1, "a", True), (2, "b", 0), (3, 0, 0)]
05

Накопленный максимум

#

Напишите функцию `running_max(numbers)`, которая возвращает список, где каждый элемент является максимальным значением, встреченным к текущему моменту в `numbers`. Используйте `itertools.accumulate`. Пример: `running_max([3, 1, 4, 1, 5, 9, 2, 6])` → `[3, 3, 4, 4, 5, 9, 9, 9]`.

from itertools import accumulate
import operator


def running_max(numbers):
    pass


print(running_max([3, 1, 4, 1, 5, 9, 2, 6]))
# [3, 3, 4, 4, 5, 9, 9, 9]
Решение
from itertools import accumulate
import operator


def running_max(numbers):
    return list(accumulate(numbers, func=max))


print(running_max([3, 1, 4, 1, 5, 9, 2, 6]))
# [3, 3, 4, 4, 5, 9, 9, 9]
06

Декартово произведение: сетка

#

Напишите функцию `grid(rows, cols)`, которая возвращает все пары координат (row, col) для сетки заданных размеров, используя `itertools.product`. Пример: `grid(2, 3)` → `[(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]`.

from itertools import product


def grid(rows, cols):
    pass


print(grid(2, 3))
# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]
Решение
from itertools import product


def grid(rows, cols):
    return list(product(range(rows), range(cols)))


print(grid(2, 3))
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
07

Все пары (комбинации)

#

Напишите функцию `all_pairs(items)`, которая возвращает все уникальные неупорядоченные пары элементов (элемент не сочетается с собой). Используйте `itertools.combinations`. Пример: `all_pairs(["A", "B", "C", "D"])` → `[("A","B"), ("A","C"), ("A","D"), ("B","C"), ("B","D"), ("C","D")]`.

from itertools import combinations


def all_pairs(items):
    pass


print(all_pairs(["A", "B", "C", "D"]))
# [("A","B"), ("A","C"), ("A","D"), ("B","C"), ("B","D"), ("C","D")]
Решение
from itertools import combinations


def all_pairs(items):
    return list(combinations(items, 2))


print(all_pairs(["A", "B", "C", "D"]))
# [("A", "B"), ("A", "C"), ("A", "D"), ("B", "C"), ("B", "D"), ("C", "D")]
08

Группировка по категории

#

Напишите функцию `group_by(items, key_func)`, которая группирует список элементов по функции ключа и возвращает словарь, отображающий каждый ключ на список элементов. Используйте `itertools.groupby` (не забудьте сначала отсортировать). Пример: с `items = ["apple", "ant", "banana", "bear", "cherry"]` и `key_func = lambda w: w[0]`, результат: `{"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}`.

from itertools import groupby


def group_by(items, key_func):
    pass


words = ["apple", "ant", "banana", "bear", "cherry"]
print(group_by(words, lambda w: w[0]))
# {"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}
Решение
from itertools import groupby


def group_by(items, key_func):
    sorted_items = sorted(items, key=key_func)
    result = {}
    for key, group in groupby(sorted_items, key=key_func):
        result[key] = list(group)
    return result


words = ["apple", "ant", "banana", "bear", "cherry"]
print(group_by(words, lambda w: w[0]))
# {"a": ["ant", "apple"], "b": ["banana", "bear"], "c": ["cherry"]}
09

takewhile: читать до разделителя

#

Напишите функцию `read_until_empty(lines)`, которая принимает список строк и возвращает только строки до первой пустой строки (или строки содержащей только пробелы), используя `itertools.takewhile`. Пример: `read_until_empty(["hello", "world", "", "more", "text"])` → `["hello", "world"]`.

from itertools import takewhile


def read_until_empty(lines):
    pass


print(read_until_empty(["hello", "world", "", "more", "text"]))
# ["hello", "world"]
Решение
from itertools import takewhile


def read_until_empty(lines):
    return list(takewhile(lambda line: line.strip() != "", lines))


print(read_until_empty(["hello", "world", "", "more", "text"]))
# ["hello", "world"]
10

Генератор паролей

#

Напишите функцию `generate_passwords(chars, length, count)`, которая генерирует `count` уникальных паролей длиной `length` символов, где каждый пароль строится из `itertools.product(chars, repeat=length)`. Возвращает первые `count` произведений как объединённые строки. Пример: `generate_passwords("ab", 2, 4)` → `["aa", "ab", "ba", "bb"]` (используйте product по порядку, без случайности).

from itertools import product, islice


def generate_passwords(chars, length, count):
    pass


print(generate_passwords("ab", 2, 4))
# ["aa", "ab", "ba", "bb"]
Решение
from itertools import product, islice


def generate_passwords(chars, length, count):
    return ["".join(p) for p in islice(product(chars, repeat=length), count)]


print(generate_passwords("ab", 2, 4))
# ["aa", "ab", "ba", "bb"]