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"]