Python · Синтаксис · Продвинутый уровень
Генераторы и itertools
Функции, возвращающие значения по одному вместо построения полного списка. Охватывает `yield`, `yield from`, генераторные выражения и itertools.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
yield from, send(), throw(), close() и бесконечные генераторы
#itertools, конвейерные паттерны и когда выбрать генератор
#Упражнения:
Генератор Фибоначчи
#Напишите функцию-генератор `fibonacci()`, которая бесконечно выдаёт числа Фибоначчи (0, 1, 1, 2, 3, 5, 8, ...). Используйте `itertools.islice`, чтобы получить первые `n` значений.
import itertools
def fibonacci():
pass
first10 = list(itertools.islice(fibonacci(), 10))
print(first10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Решение
import itertools
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
first10 = list(itertools.islice(fibonacci(), 10))
print(first10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Генератор с дробным шагом
#Напишите функцию-генератор `frange(start, stop, step)`, которая выдаёт вещественные числа от `start` до `stop` (не включая) с шагом `step`. Она должна работать как `range()`, но поддерживать дробный шаг.
def frange(start, stop, step):
pass
print(list(frange(0, 1, 0.25))) # [0, 0.25, 0.5, 0.75]
print(list(frange(1, 2, 0.3))) # [1, 1.3, 1.6, 1.9]
Решение
def frange(start, stop, step):
current = start
while current < stop:
yield current
current += step
print(list(frange(0, 1, 0.25))) # [0, 0.25, 0.5, 0.75]
print(list(frange(1, 2, 0.3))) # [1, 1.3, 1.6000000000000001, 1.9]
Разглаживание вложенных списков через yield from
#Напишите генератор `flatten(nested)`, который рекурсивно разглаживает вложенный список любой глубины и выдаёт отдельные элементы. Используйте `yield from` для рекурсии. Не-списки выдаются как есть.
def flatten(nested):
pass
data = [1, [2, 3], [4, [5, 6]], [[7], 8]]
print(list(flatten(data))) # [1, 2, 3, 4, 5, 6, 7, 8]
Решение
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
data = [1, [2, 3], [4, [5, 6]], [[7], 8]]
print(list(flatten(data))) # [1, 2, 3, 4, 5, 6, 7, 8]
Скользящее среднее через send()
#Напишите генератор-корутину `running_average()`, вычисляющую скользящее среднее. Каждый вызов `.send(value)` должен добавлять `value` к сумме и `yield`-нуть текущее среднее. Запустите генератор через `next()` перед отправкой значений.
def running_average():
pass
avg = running_average()
next(avg) # запускаем корутину
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0
print(avg.send(30)) # 20.0
Решение
def running_average():
total = 0
count = 0
value = yield # точка запуска — первый next() останавливается здесь
while True:
total += value
count += 1
value = yield total / count
avg = running_average()
next(avg) # продвигаемся до первого yield
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0
print(avg.send(30)) # 20.0
Генератор скользящего окна
#Напишите генератор `sliding_window(iterable, n)`, который выдаёт кортежи из `n` последовательных элементов, сдвигаясь на один шаг. Например, `sliding_window([1,2,3,4,5], 3)` выдаёт `(1,2,3)`, `(2,3,4)`, `(3,4,5)`.
from collections import deque
def sliding_window(iterable, n):
pass
print(list(sliding_window([1, 2, 3, 4, 5], 3)))
# [(1, 2, 3), (2, 3, 4), (3, 4, 5)]
Решение
from collections import deque
def sliding_window(iterable, n):
window = deque(maxlen=n)
for item in iterable:
window.append(item)
if len(window) == n:
yield tuple(window)
print(list(sliding_window([1, 2, 3, 4, 5], 3)))
# [(1, 2, 3), (2, 3, 4), (3, 4, 5)]
Генераторы take и drop
#Напишите две функции-генератора: - `take(n, iterable)` — выдаёт первые `n` элементов из iterable. - `drop(n, iterable)` — пропускает первые `n` элементов и выдаёт остальные. Обе должны работать лениво (не конвертировать в список внутри).
def take(n, iterable):
pass
def drop(n, iterable):
pass
nums = range(10)
print(list(take(3, nums))) # [0, 1, 2]
print(list(drop(7, nums))) # [7, 8, 9]
Решение
def take(n, iterable):
for i, item in enumerate(iterable):
if i >= n:
break
yield item
def drop(n, iterable):
for i, item in enumerate(iterable):
if i >= n:
yield item
nums = range(10)
print(list(take(3, nums))) # [0, 1, 2]
print(list(drop(7, nums))) # [7, 8, 9]
# Или через itertools:
import itertools
list(itertools.islice(nums, 3)) # take(3)
list(itertools.islice(nums, 7, None)) # drop(7)
Генератор фрагментов (chunks)
#Напишите генератор `chunks(iterable, n)`, который разбивает iterable на последовательные фрагменты размером `n`. Последний фрагмент может быть меньше, если длина iterable не делится на `n`. Выдавайте каждый фрагмент как список.
def chunks(iterable, n):
pass
print(list(chunks(range(10), 3)))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Решение
def chunks(iterable, n):
chunk = []
for item in iterable:
chunk.append(item)
if len(chunk) == n:
yield chunk
chunk = []
if chunk: # выдаём последний неполный фрагмент
yield chunk
print(list(chunks(range(10), 3)))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
# Python 3.12+: itertools.batched(iterable, n)
Генератор простых чисел
#Напишите генератор `primes()`, который бесконечно выдаёт простые числа с помощью простого пробного деления: для каждого кандидата проверяйте делимость только на ранее найденные простые числа. Используйте `itertools.islice` для первых 10 простых.
import itertools
def primes():
pass
print(list(itertools.islice(primes(), 10)))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Решение
import itertools
def primes():
found = []
candidate = 2
while True:
if all(candidate % p != 0 for p in found):
found.append(candidate)
yield candidate
candidate += 1
print(list(itertools.islice(primes(), 10)))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Пайплайн из itertools
#С помощью `itertools.chain`, `itertools.filterfalse` и генераторного выражения постройте пайплайн, который: 1. Объединяет два списка: `[1, 2, 3]` и `[4, 5, 6]`. 2. Фильтрует чётные числа. 3. Возводит оставшиеся числа в квадрат. Выведите результат как список.
import itertools a = [1, 2, 3] b = [4, 5, 6] # Постройте пайплайн с itertools.chain, itertools.filterfalse # и генераторным выражением для возведения в квадрат. result = [] print(result) # [1, 9, 25]
Решение
import itertools
a = [1, 2, 3]
b = [4, 5, 6]
chained = itertools.chain(a, b) # 1 2 3 4 5 6
odds = itertools.filterfalse(lambda x: x % 2 == 0, chained) # 1 3 5
result = list(x * x for x in odds) # 1 9 25
print(result) # [1, 9, 25]
Группировка слов по первой букве через itertools.groupby
#Имея отсортированный список слов, используйте `itertools.groupby`, чтобы сгруппировать их по первой букве и вывести каждую группу на отдельной строке: `'a: apple, avocado'`. Список уже отсортирован по алфавиту.
import itertools words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'coconut'] # Используйте itertools.groupby для группировки по первой букве # Ожидаемый вывод: # a: apple, avocado # b: banana, blueberry # c: cherry, coconut
Решение
import itertools
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'coconut']
for letter, group in itertools.groupby(words, key=lambda w: w[0]):
print(f'{letter}: {", ".join(group)}')