itertools — Функции, создающие итераторы для эффективного цикла


Этот модуль реализует ряд строительных блоков iterator, вдохновленных конструкциями из APL, Haskell и SML. Каждый из них был переделан в форме, подходящей для Python.

Модуль стандартизирует основной набор быстрых, экономящих память инструментов, которые полезны как сами по себе, так и в комбинации. Вместе они образуют «алгебру итераторов», позволяющую лаконично и эффективно конструировать специализированные инструменты на чистом Python.

Например, SML предоставляет инструмент табуляции: tabulate(f), который создает последовательность f(0), f(1), .... Того же эффекта можно добиться в Python, объединив map() и count() в последовательность map(f, count()).

Эти инструменты и их встроенные аналоги также хорошо работают с высокоскоростными функциями в модуле operator. Например, оператор умножения может быть перенесен на два вектора для формирования эффективного точечного продукта: sum(starmap(operator.mul, zip(vec1, vec2, strict=True))).

Бесконечные итераторы:.

Итератор

Аргументы

Результаты

Пример

count()

[начало[, шаг]]

старт, старт+шаг, старт+2*шаг, …

count(10) 10 11 12 13 14 ...

cycle()

p

p0, p1, … пласт, p0, p1, …

cycle('ABCD') A B C D A B C D ...

repeat()

elem [,n]

elem, elem, elem, … бесконечно или до n раз

repeat(10, 3) 10 10 10

Итераторы, заканчивающиеся на кратчайшей входной последовательности:.

Итератор

Аргументы

Результаты

Пример

accumulate()

p [,func]

p0, p0+p1, p0+p1+p2, …

accumulate([1,2,3,4,5]) 1 3 6 10 15

batched()

п, н

(p0, p1, …, p_n-1), …

batched('ABCDEFG', n=3) ABC DEF G

chain()

p, q, …

p0, p1, … plast, q0, q1, …

chain('ABC', 'DEF') A B C D E F

chain.from_iterable()

итерируемый

p0, p1, … plast, q0, q1, …

chain.from_iterable(['ABC', 'DEF']) A B C D E F

compress()

данные, селекторы

(d[0], если s[0]), (d[1], если s[1]), …

compress('ABCDEF', [1,0,1,0,1,1]) A C E F

dropwhile()

предикат, сегмент

seq[n], seq[n+1], начиная с момента неудачи предиката

dropwhile(lambda x: x<5, [1,4,6,3,8]) 6 3 8

filterfalse()

предикат, сегмент

элементы seq, в которых predicate(elem) не работает

filterfalse(lambda x: x<5, [1,4,6,3,8]) 6 8

groupby()

iterable[, key]

субитераторы, сгруппированные по значению ключа(v)

islice()

seq, [start,] stop [, step]

элементы из seq[start:stop:step]

islice('ABCDEFG', 2, None) C D E F G

pairwise()

итерируемый

(p[0], p[1]), (p[1], p[2])

pairwise('ABCDEFG') AB BC CD DE EF FG

starmap()

func, seq

func(*seq[0]), func(*seq[1]), …

starmap(pow, [(2,5), (3,2), (10,3)]) 32 9 1000

takewhile()

предикат, сегмент

seq[0], seq[1], пока предикат не сработает

takewhile(lambda x: x<5, [1,4,6,3,8]) 1 4

tee()

это, н

it1, it2, … itn разбивает один итератор на n

zip_longest()

p, q, …

(p[0], q[0]), (p[1], q[1]), …

zip_longest('ABCD', 'xy', fillvalue='-') Ax By C- D-

Комбинаторные итераторы:.

Итератор

Аргументы

Результаты

product()

p, q, … [repeat=1]

картезианское произведение, эквивалентное вложенному циклу for-loop

permutations()

p[, r]

кортежи длиной r, все возможные упорядочения, без повторяющихся элементов

combinations()

п, р

кортежи длиной r, в отсортированном порядке, без повторяющихся элементов

combinations_with_replacement()

п, р

кортежи длины r, отсортированные по порядку, с повторяющимися элементами

Примеры

Результаты

product('ABCD', repeat=2)

AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD

permutations('ABCD', 2)

AB AC AD BA BC BD CA CB CD DA DB DC

combinations('ABCD', 2)

AB AC AD BC BD CD

combinations_with_replacement('ABCD', 2)

AA AB AC AD BB BC BD CC CD DD

Функции Itertool

Все следующие функции модуля строят и возвращают итераторы. Некоторые из них предоставляют потоки бесконечной длины, поэтому доступ к ним должен осуществляться только с помощью функций или циклов, которые усекают поток.

itertools.accumulate(iterable[, function, *, initial=None])

Создайте итератор, возвращающий накопленные суммы или накопленные результаты других двоичных функций.

По умолчанию функция принимает значение сложения. Функция function должна принимать два аргумента, накопленный итог и значение из iterable.

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

Примерно эквивалентно:

def accumulate(iterable, function=operator.add, *, initial=None):
    'Return running totals'
    # accumulate([1,2,3,4,5]) → 1 3 6 10 15
    # accumulate([1,2,3,4,5], initial=100) → 100 101 103 106 110 115
    # accumulate([1,2,3,4,5], operator.mul) → 1 2 6 24 120

    iterator = iter(iterable)
    total = initial
    if initial is None:
        try:
            total = next(iterator)
        except StopIteration:
            return

    yield total
    for element in iterator:
        total = function(total, element)
        yield total

Аргумент function может быть установлен в min() для текущего минимума, max() для текущего максимума или operator.mul() для текущего продукта. Amortization tables может быть построен путем накопления процентов и применения платежей:

>>> data = [3, 4, 6, 2, 1, 9, 0, 7, 5, 8]
>>> list(accumulate(data, max))              # running maximum
[3, 4, 6, 6, 6, 9, 9, 9, 9, 9]
>>> list(accumulate(data, operator.mul))     # running product
[3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]

# Amortize a 5% loan of 1000 with 10 annual payments of 90
>>> update = lambda balance, payment: round(balance * 1.05) - payment
>>> list(accumulate(repeat(90, 10), update, initial=1_000))
[1000, 960, 918, 874, 828, 779, 728, 674, 618, 559, 497]

Смотрите functools.reduce() для аналогичной функции, которая возвращает только конечное накопленное значение.

Added in version 3.2.

Изменено в версии 3.3: Добавлен необязательный параметр function.

Изменено в версии 3.8: Добавлен необязательный параметр initial.

itertools.batched(iterable, n, *, strict=False)

Пакетная обработка данных из iterable в кортежи длины n. Последний пакет может быть короче, чем n.

Если значение strict равно true, то будет вызвана ошибка ValueError, если конечная партия короче, чем n.

Перебирает входную итерируемую переменную и накапливает данные в кортежи размером до n. Входные данные потребляются лениво, достаточно, чтобы заполнить пакет. Результат выдается, как только пакет будет заполнен или когда входная итерируемая таблица будет исчерпана:

>>> flattened_data = ['roses', 'red', 'violets', 'blue', 'sugar', 'sweet']
>>> unflattened = list(batched(flattened_data, 2))
>>> unflattened
[('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]

Примерно эквивалентно:

def batched(iterable, n, *, strict=False):
    # batched('ABCDEFG', 3) → ABC DEF G
    if n < 1:
        raise ValueError('n must be at least one')
    iterator = iter(iterable)
    while batch := tuple(islice(iterator, n)):
        if strict and len(batch) != n:
            raise ValueError('batched(): incomplete batch')
        yield batch

Added in version 3.12.

Изменено в версии 3.13: Добавлена опция strict.

itertools.chain(*iterables)

Создайте итератор, который возвращает элементы из первой итерируемой таблицы, пока она не будет исчерпана, затем переходит к следующей итерируемой таблице, пока все итерируемые таблицы не будут исчерпаны. Используется для обработки последовательных последовательностей как одной последовательности. Примерно эквивалентен:

def chain(*iterables):
    # chain('ABC', 'DEF') → A B C D E F
    for iterable in iterables:
        yield from iterable
classmethod chain.from_iterable(iterable)

Альтернативный конструктор для chain(). Получает цепочку входов из одного итерируемого аргумента, который оценивается лениво. Примерно эквивалентно:

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) → A B C D E F
    for iterable in iterables:
        yield from iterable
itertools.combinations(iterable, r)

Возвращает подпоследовательности элементов длины r из входного iterable.

На выходе получается подпоследовательность product(), содержащая только те записи, которые являются подпоследовательностями цитируемого. Длина выхода задается math.comb(), который вычисляет n! / r! / (n - r)!, если 0 r n, или ноль, если r > n.

Комбинированные кортежи выдаются в лексикографическом порядке в соответствии с порядком входного iterable. Если входной iterable отсортирован, то выходные кортежи будут выданы в отсортированном порядке.

Элементы считаются уникальными на основании их положения, а не значения. Если элементы ввода уникальны, то в каждой комбинации не будет повторяющихся значений.

Примерно эквивалентно:

def combinations(iterable, r):
    # combinations('ABCD', 2) → AB AC AD BC BD CD
    # combinations(range(4), 3) → 012 013 023 123

    pool = tuple(iterable)
    n = len(pool)
    if r > n:
        return
    indices = list(range(r))

    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + n - r:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        yield tuple(pool[i] for i in indices)
itertools.combinations_with_replacement(iterable, r)

Возвращает подпоследовательности элементов длины r из входного iterable, позволяя отдельным элементам повторяться более одного раза.

На выходе получается подпоследовательность product(), содержащая только те записи, которые являются подпоследовательностями (с возможными повторяющимися элементами) итерабельного. Номер возвращаемой подпоследовательности - (n + r - 1)! / r! / (n - 1)!, если n > 0.

Комбинированные кортежи выдаются в лексикографическом порядке в соответствии с порядком входного iterable. если входной iterable отсортирован, то выходные кортежи будут выданы в отсортированном порядке.

Элементы считаются уникальными на основании их положения, а не значения. Если входные элементы уникальны, сгенерированные комбинации также будут уникальными.

Примерно эквивалентно:

def combinations_with_replacement(iterable, r):
    # combinations_with_replacement('ABC', 2) → AA AB AC BB BC CC

    pool = tuple(iterable)
    n = len(pool)
    if not n and r:
        return
    indices = [0] * r

    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != n - 1:
                break
        else:
            return
        indices[i:] = [indices[i] + 1] * (r - i)
        yield tuple(pool[i] for i in indices)

Added in version 3.1.

itertools.compress(data, selectors)

Создайте итератор, возвращающий элементы из data, для которых соответствующий элемент в selectors равен true. Останавливается, когда итераторы data или selectors будут исчерпаны. Примерно эквивалентно:

def compress(data, selectors):
    # compress('ABCDEF', [1,0,1,0,1,1]) → A C E F
    return (datum for datum, selector in zip(data, selectors) if selector)

Added in version 3.1.

itertools.count(start=0, step=1)

Создайте итератор, возвращающий равномерно распределенные значения, начиная с start. Может использоваться с map() для генерации последовательных точек данных или с zip() для добавления порядковых номеров. Примерно эквивалентно:

def count(start=0, step=1):
    # count(10) → 10 11 12 13 14 ...
    # count(2.5, 0.5) → 2.5 3.0 3.5 ...
    n = start
    while True:
        yield n
        n += step

При подсчете с помощью чисел с плавающей запятой иногда можно добиться большей точности, заменив мультипликативный код, например: (start + step * i for i in count()).

Изменено в версии 3.1: Добавлен аргумент шаг и разрешены нецелые аргументы.

itertools.cycle(iterable)

Создайте итератор, возвращающий элементы из iterable и сохраняющий копию каждого из них. Когда итератор будет исчерпан, верните элементы из сохраненной копии. Повторяется бесконечно. Примерно эквивалентно:

def cycle(iterable):
    # cycle('ABCD') → A B C D A B C D A B C D ...
    saved = []
    for element in iterable:
        yield element
        saved.append(element)
    while saved:
        for element in saved:
            yield element

Этот итертул может потребовать значительного объема вспомогательной памяти (в зависимости от длины итерируемой переменной).

itertools.dropwhile(predicate, iterable)

Создайте итератор, который удаляет элементы из iterable, пока предикат истинен, а затем возвращает каждый элемент. Примерно эквивалентно:

def dropwhile(predicate, iterable):
    # dropwhile(lambda x: x<5, [1,4,6,3,8]) → 6 3 8

    iterator = iter(iterable)
    for x in iterator:
        if not predicate(x):
            yield x
            break

    for x in iterator:
        yield x

Обратите внимание, что он не выдает любой результат, пока предикат не станет ложным, поэтому этот итертул может иметь длительное время запуска.

itertools.filterfalse(predicate, iterable)

Создайте итератор, который будет фильтровать элементы из iterable, возвращая только те, для которых предикат возвращает ложное значение. Если предикат равен None, возвращаются элементы, которые являются ложными. Примерно эквивалентно:

def filterfalse(predicate, iterable):
    # filterfalse(lambda x: x<5, [1,4,6,3,8]) → 6 8
    if predicate is None:
        predicate = bool
    for x in iterable:
        if not predicate(x):
            yield x
itertools.groupby(iterable, key=None)

Создайте итератор, возвращающий последовательные ключи и группы из iterable. Ключ key - это функция, вычисляющая значение ключа для каждого элемента. Если она не указана или равна None, то key по умолчанию является функцией тождества и возвращает элемент без изменений. Как правило, итерируемое множество уже должно быть отсортировано по той же функции ключа.

Работа groupby() аналогична фильтру uniq в Unix. Он генерирует разбиение или новую группу каждый раз, когда изменяется значение ключевой функции (вот почему обычно необходимо отсортировать данные с помощью той же ключевой функции). Такое поведение отличается от GROUP BY в SQL, который объединяет общие элементы независимо от порядка их ввода.

Возвращаемая группа сама является итератором, который разделяет базовую итерируемую переменную с groupby(). Поскольку источник является общим, при продвижении объекта groupby() предыдущая группа больше не видна. Поэтому, если эти данные понадобятся позже, их следует хранить в виде списка:

groups = []
uniquekeys = []
data = sorted(data, key=keyfunc)
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

groupby() примерно равно:

def groupby(iterable, key=None):
    # [k for k, g in groupby('AAAABBBCCDAABBB')] → A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] → AAAA BBB CC D

    keyfunc = (lambda x: x) if key is None else key
    iterator = iter(iterable)
    exhausted = False

    def _grouper(target_key):
        nonlocal curr_value, curr_key, exhausted
        yield curr_value
        for curr_value in iterator:
            curr_key = keyfunc(curr_value)
            if curr_key != target_key:
                return
            yield curr_value
        exhausted = True

    try:
        curr_value = next(iterator)
    except StopIteration:
        return
    curr_key = keyfunc(curr_value)

    while not exhausted:
        target_key = curr_key
        curr_group = _grouper(target_key)
        yield curr_key, curr_group
        if curr_key == target_key:
            for _ in curr_group:
                pass
itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])

Создайте итератор, который возвращает выбранные элементы из итерируемой таблицы. Работает как нарезка последовательности, но не поддерживает отрицательные значения для start, stop или step.

Если start равно нулю или None, итерация начинается с нуля. В противном случае элементы из итерируемой таблицы пропускаются до тех пор, пока не будет достигнуто start.

Если stop равно None, итерация продолжается до тех пор, пока итератор не будет исчерпан, если вообще будет исчерпан. В противном случае она останавливается на указанной позиции.

Если step имеет значение None, шаг по умолчанию равен единице. Элементы возвращаются последовательно, если только step не установлен больше единицы, что приводит к пропуску элементов.

Примерно эквивалентно:

def islice(iterable, *args):
    # islice('ABCDEFG', 2) → A B
    # islice('ABCDEFG', 2, 4) → C D
    # islice('ABCDEFG', 2, None) → C D E F G
    # islice('ABCDEFG', 0, None, 2) → A C E G

    s = slice(*args)
    start = 0 if s.start is None else s.start
    stop = s.stop
    step = 1 if s.step is None else s.step
    if start < 0 or (stop is not None and stop < 0) or step <= 0:
        raise ValueError

    indices = count() if stop is None else range(max(start, stop))
    next_i = start
    for i, element in zip(indices, iterable):
        if i == next_i:
            yield element
            next_i += step
itertools.pairwise(iterable)

Возвращает последовательные пересекающиеся пары, взятые из входной iterable.

Количество 2-кортежей в выходном итераторе будет на один меньше, чем количество входных. Он будет пуст, если во входном итераторе меньше двух значений.

Примерно эквивалентно:

def pairwise(iterable):
    # pairwise('ABCDEFG') → AB BC CD DE EF FG
    iterator = iter(iterable)
    a = next(iterator, None)
    for b in iterator:
        yield a, b
        a = b

Added in version 3.10.

itertools.permutations(iterable, r=None)

Возвращает последовательные r длины permutations of elements из iterable.

Если r не указан или равен None, то r по умолчанию равняется длине итерабельного, и генерируются все возможные полноразмерные перестановки.

На выходе получается подпоследовательность product(), в которой отфильтрованы записи с повторяющимися элементами. Длина выхода задается math.perm(), который вычисляет n! / (n - r)!, если 0 r n, или ноль, если r > n.

Кортежи перестановки выдаются в лексикографическом порядке в соответствии с порядком входного iterable. Если входной iterable отсортирован, то выходные кортежи будут выданы в отсортированном порядке.

Элементы считаются уникальными по их расположению, а не по их значению. Если входные элементы уникальны, то в перестановке не будет повторяющихся значений.

Примерно эквивалентно:

def permutations(iterable, r=None):
    # permutations('ABCD', 2) → AB AC AD BA BC BD CA CB CD DA DB DC
    # permutations(range(3)) → 012 021 102 120 201 210

    pool = tuple(iterable)
    n = len(pool)
    r = n if r is None else r
    if r > n:
        return

    indices = list(range(n))
    cycles = list(range(n, n-r, -1))
    yield tuple(pool[i] for i in indices[:r])

    while n:
        for i in reversed(range(r)):
            cycles[i] -= 1
            if cycles[i] == 0:
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
            else:
                j = cycles[i]
                indices[i], indices[-j] = indices[-j], indices[i]
                yield tuple(pool[i] for i in indices[:r])
                break
        else:
            return
itertools.product(*iterables, repeat=1)

Декартово произведение входных итераций.

Примерно эквивалентны вложенным циклам for в выражении-генераторе. Например, product(A, B) возвращает то же самое, что и ((x,y) for x in A for y in B).

Вложенные циклы работают как одометр, причем крайний правый элемент продвигается вперед на каждой итерации. Этот паттерн создает лексикографическое упорядочивание, так что если итерабельные переменные на входе отсортированы, то кортежи продуктов будут выдаваться в отсортированном порядке.

Чтобы вычислить произведение итерируемой переменной на саму себя, укажите количество повторений с помощью необязательного ключевого слова repeat. Например, product(A, repeat=4) означает то же самое, что и product(A, A, A, A).

Эта функция примерно эквивалентна следующему коду, за исключением того, что в реальной реализации промежуточные результаты не накапливаются в памяти:

def product(*iterables, repeat=1):
    # product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy
    # product(range(2), repeat=3) → 000 001 010 011 100 101 110 111

    pools = [tuple(pool) for pool in iterables] * repeat

    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]

    for prod in result:
        yield tuple(prod)

Перед запуском product() полностью расходует входные итерации, сохраняя в памяти пулы значений для генерации произведений. Соответственно, он полезен только при конечных входных данных.

itertools.repeat(object[, times])

Создайте итератор, который возвращает объект снова и снова. Выполняется бесконечно, если не указан аргумент times.

Примерно эквивалентно:

def repeat(object, times=None):
    # repeat(10, 3) → 10 10 10
    if times is None:
        while True:
            yield object
    else:
        for i in range(times):
            yield object

Обычно repeat используется для передачи потока константных значений в map или zip:

>>> list(map(pow, range(10), repeat(2)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
itertools.starmap(function, iterable)

Создайте итератор, который вычисляет функцию, используя аргументы, полученные из iterable. Используется вместо map(), когда параметры аргументов уже «упакованы» в кортежи.

Различие между map() и starmap() аналогично различию между function(a,b) и function(*c). Примерно эквивалентно:

def starmap(function, iterable):
    # starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000
    for args in iterable:
        yield function(*args)
itertools.takewhile(predicate, iterable)

Создайте итератор, возвращающий элементы из iterable при условии, что предикат истинен. Примерно эквивалентно:

def takewhile(predicate, iterable):
    # takewhile(lambda x: x<5, [1,4,6,3,8]) → 1 4
    for x in iterable:
        if not predicate(x):
            break
        yield x

Обратите внимание, что элемент, который первым не выполнил условие предиката, потребляется из входного итератора, и нет возможности получить к нему доступ. Это может стать проблемой, если приложение захочет продолжить потребление входного итератора после того, как takewhile будет запущен до исчерпания. Чтобы обойти эту проблему, используйте вместо нее more-iterools before_and_after().

itertools.tee(iterable, n=2)

Возвращает n независимых итераторов из одной итерируемой таблицы.

Примерно эквивалентно:

def tee(iterable, n=2):
    iterator = iter(iterable)
    shared_link = [None, None]
    return tuple(_tee(iterator, shared_link) for _ in range(n))

def _tee(iterator, link):
    try:
        while True:
            if link[1] is None:
                link[0] = next(iterator)
                link[1] = [None, None]
            value, link = link
            yield value
    except StopIteration:
        return

После создания tee() исходный iterable больше нигде не должен использоваться; в противном случае iterable может быть расширен без уведомления об этом объектов tee.

Итераторы tee не являются потокобезопасными. Может возникнуть ошибка RuntimeError при одновременном использовании итераторов, возвращаемых одним и тем же вызовом tee(), даже если исходный iterable является потокобезопасным.

Этот итератор может потребовать значительного объема вспомогательной памяти (в зависимости от того, сколько временных данных необходимо хранить). В общем случае, если один итератор использует большую часть или все данные до начала работы другого итератора, быстрее использовать list() вместо tee().

itertools.zip_longest(*iterables, fillvalue=None)

Создайте итератор, который объединяет элементы каждой из итераций.

Если итерабели имеют неодинаковую длину, недостающие значения заполняются fillvalue. Если значение не указано, fillvalue по умолчанию принимает значение None.

Итерация продолжается до тех пор, пока не будет исчерпана самая длинная итерабельность.

Примерно эквивалентно:

def zip_longest(*iterables, fillvalue=None):
    # zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-

    iterators = list(map(iter, iterables))
    num_active = len(iterators)
    if not num_active:
        return

    while True:
        values = []
        for i, iterator in enumerate(iterators):
            try:
                value = next(iterator)
            except StopIteration:
                num_active -= 1
                if not num_active:
                    return
                iterators[i] = repeat(fillvalue)
                value = fillvalue
            values.append(value)
        yield tuple(values)

Если одна из итераций потенциально бесконечна, то функцию zip_longest() следует обернуть чем-то, что ограничивает количество вызовов (например, islice() или takewhile()).

Рецепты Itertools

В этом разделе приведены рецепты создания расширенного набора инструментов с использованием существующих itertools в качестве строительных блоков.

Основная цель рецептов itertools - образовательная. Рецепты показывают различные способы мышления об отдельных инструментах - например, что chain.from_iterable связан с концепцией сплющивания. Рецепты также дают представление о том, как можно комбинировать инструменты - например, как starmap() и repeat() могут работать вместе. В рецептах также показаны шаблоны использования itertools с модулями operator и collections, а также со встроенными itertools, такими как map(), filter(), reversed() и enumerate().

Второстепенная цель рецептов - служить инкубатором. Итертулы accumulate(), compress() и pairwise() начинались как рецепты. В настоящее время рецепты sliding_window(), iter_index() и sieve() проходят тестирование, чтобы понять, насколько они оправдывают себя.

Практически все эти и многие, многие другие рецепты можно установить из проекта more-itertools, найденного в индексе пакетов Python:

python -m pip install more-itertools

Многие из рецептов обладают такой же высокой производительностью, как и основной инструментарий. Высокая производительность памяти поддерживается за счет обработки элементов по одному, а не за счет одновременного ввода в память всего итерируемого. Объем кода остается небольшим за счет объединения инструментов в functional style. Высокая скорость сохраняется за счет предпочтения «векторизованных» строительных блоков перед использованием циклов for и generators, которые вызывают накладные расходы интерпретатора.

import collections
import contextlib
import functools
import math
import operator
import random

def take(n, iterable):
    "Return first n items of the iterable as a list."
    return list(islice(iterable, n))

def prepend(value, iterable):
    "Prepend a single value in front of an iterable."
    # prepend(1, [2, 3, 4]) → 1 2 3 4
    return chain([value], iterable)

def tabulate(function, start=0):
    "Return function(0), function(1), ..."
    return map(function, count(start))

def repeatfunc(func, times=None, *args):
    "Repeat calls to func with specified arguments."
    if times is None:
        return starmap(func, repeat(args))
    return starmap(func, repeat(args, times))

def flatten(list_of_lists):
    "Flatten one level of nesting."
    return chain.from_iterable(list_of_lists)

def ncycles(iterable, n):
    "Returns the sequence elements n times."
    return chain.from_iterable(repeat(tuple(iterable), n))

def tail(n, iterable):
    "Return an iterator over the last n items."
    # tail(3, 'ABCDEFG') → E F G
    return iter(collections.deque(iterable, maxlen=n))

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        collections.deque(iterator, maxlen=0)
    else:
        next(islice(iterator, n, n), None)

def nth(iterable, n, default=None):
    "Returns the nth item or a default value."
    return next(islice(iterable, n, None), default)

def quantify(iterable, predicate=bool):
    "Given a predicate that returns True or False, count the True results."
    return sum(map(predicate, iterable))

def first_true(iterable, default=False, predicate=None):
    "Returns the first true value or the *default* if there is no true value."
    # first_true([a,b,c], x) → a or b or c or x
    # first_true([a,b], x, f) → a if f(a) else b if f(b) else x
    return next(filter(predicate, iterable), default)

def all_equal(iterable, key=None):
    "Returns True if all the elements are equal to each other."
    # all_equal('4٤௪౪໔', key=int) → True
    return len(take(2, groupby(iterable, key))) <= 1

def unique_justseen(iterable, key=None):
    "Yield unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') → A B C D A B
    # unique_justseen('ABBcCAD', str.casefold) → A B c A D
    if key is None:
        return map(operator.itemgetter(0), groupby(iterable))
    return map(next, map(operator.itemgetter(1), groupby(iterable, key)))

def unique_everseen(iterable, key=None):
    "Yield unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') → A B C D
    # unique_everseen('ABBcCAD', str.casefold) → A B c D
    seen = set()
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen.add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen.add(k)
                yield element

def unique(iterable, key=None, reverse=False):
   "Yield unique elements in sorted order. Supports unhashable inputs."
   # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4]
   return unique_justseen(sorted(iterable, key=key, reverse=reverse), key=key)

def sliding_window(iterable, n):
    "Collect data into overlapping fixed-length chunks or blocks."
    # sliding_window('ABCDEFG', 4) → ABCD BCDE CDEF DEFG
    iterator = iter(iterable)
    window = collections.deque(islice(iterator, n - 1), maxlen=n)
    for x in iterator:
        window.append(x)
        yield tuple(window)

def grouper(iterable, n, *, incomplete='fill', fillvalue=None):
    "Collect data into non-overlapping fixed-length chunks or blocks."
    # grouper('ABCDEFG', 3, fillvalue='x') → ABC DEF Gxx
    # grouper('ABCDEFG', 3, incomplete='strict') → ABC DEF ValueError
    # grouper('ABCDEFG', 3, incomplete='ignore') → ABC DEF
    iterators = [iter(iterable)] * n
    match incomplete:
        case 'fill':
            return zip_longest(*iterators, fillvalue=fillvalue)
        case 'strict':
            return zip(*iterators, strict=True)
        case 'ignore':
            return zip(*iterators)
        case _:
            raise ValueError('Expected fill, strict, or ignore')

def roundrobin(*iterables):
    "Visit input iterables in a cycle until each is exhausted."
    # roundrobin('ABC', 'D', 'EF') → A D E B F C
    # Algorithm credited to George Sakkis
    iterators = map(iter, iterables)
    for num_active in range(len(iterables), 0, -1):
        iterators = cycle(islice(iterators, num_active))
        yield from map(next, iterators)

def partition(predicate, iterable):
    """Partition entries into false entries and true entries.

    If *predicate* is slow, consider wrapping it with functools.lru_cache().
    """
    # partition(is_odd, range(10)) → 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(predicate, t1), filter(predicate, t2)

def subslices(seq):
    "Return all contiguous non-empty subslices of a sequence."
    # subslices('ABCD') → A AB ABC ABCD B BC BCD C CD D
    slices = starmap(slice, combinations(range(len(seq) + 1), 2))
    return map(operator.getitem, repeat(seq), slices)

def iter_index(iterable, value, start=0, stop=None):
    "Return indices where a value occurs in a sequence or iterable."
    # iter_index('AABCADEAF', 'A') → 0 1 4 7
    seq_index = getattr(iterable, 'index', None)
    if seq_index is None:
        iterator = islice(iterable, start, stop)
        for i, element in enumerate(iterator, start):
            if element is value or element == value:
                yield i
    else:
        stop = len(iterable) if stop is None else stop
        i = start
        with contextlib.suppress(ValueError):
            while True:
                yield (i := seq_index(value, i, stop))
                i += 1

def iter_except(func, exception, first=None):
    "Convert a call-until-exception interface to an iterator interface."
    # iter_except(d.popitem, KeyError) → non-blocking dictionary iterator
    with contextlib.suppress(exception):
        if first is not None:
            yield first()
        while True:
            yield func()

Следующие рецепты имеют более математический вкус:

def powerset(iterable):
    "powerset([1,2,3]) → () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

def sum_of_squares(iterable):
    "Add up the squares of the input values."
    # sum_of_squares([10, 20, 30]) → 1400
    return math.sumprod(*tee(iterable))

def reshape(matrix, cols):
    "Reshape a 2-D matrix to have a given number of columns."
    # reshape([(0, 1), (2, 3), (4, 5)], 3) →  (0, 1, 2), (3, 4, 5)
    return batched(chain.from_iterable(matrix), cols, strict=True)

def transpose(matrix):
    "Swap the rows and columns of a 2-D matrix."
    # transpose([(1, 2, 3), (11, 22, 33)]) → (1, 11) (2, 22) (3, 33)
    return zip(*matrix, strict=True)

def matmul(m1, m2):
    "Multiply two matrices."
    # matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]) → (49, 80), (41, 60)
    n = len(m2[0])
    return batched(starmap(math.sumprod, product(m1, transpose(m2))), n)

def convolve(signal, kernel):
    """Discrete linear convolution of two iterables.
    Equivalent to polynomial multiplication.

    Convolutions are mathematically commutative; however, the inputs are
    evaluated differently.  The signal is consumed lazily and can be
    infinite. The kernel is fully consumed before the calculations begin.

    Article:  https://betterexplained.com/articles/intuitive-convolution/
    Video:    https://www.youtube.com/watch?v=KuXjwB4LzSA
    """
    # convolve([1, -1, -20], [1, -3]) → 1 -4 -17 60
    # convolve(data, [0.25, 0.25, 0.25, 0.25]) → Moving average (blur)
    # convolve(data, [1/2, 0, -1/2]) → 1st derivative estimate
    # convolve(data, [1, -2, 1]) → 2nd derivative estimate
    kernel = tuple(kernel)[::-1]
    n = len(kernel)
    padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1))
    windowed_signal = sliding_window(padded_signal, n)
    return map(math.sumprod, repeat(kernel), windowed_signal)

def polynomial_from_roots(roots):
    """Compute a polynomial's coefficients from its roots.

       (x - 5) (x + 4) (x - 3)  expands to:   x³ -4x² -17x + 60
    """
    # polynomial_from_roots([5, -4, 3]) → [1, -4, -17, 60]
    factors = zip(repeat(1), map(operator.neg, roots))
    return list(functools.reduce(convolve, factors, [1]))

def polynomial_eval(coefficients, x):
    """Evaluate a polynomial at a specific value.

    Computes with better numeric stability than Horner's method.
    """
    # Evaluate x³ -4x² -17x + 60 at x = 5
    # polynomial_eval([1, -4, -17, 60], x=5) → 0
    n = len(coefficients)
    if not n:
        return type(x)(0)
    powers = map(pow, repeat(x), reversed(range(n)))
    return math.sumprod(coefficients, powers)

def polynomial_derivative(coefficients):
    """Compute the first derivative of a polynomial.

       f(x)  =  x³ -4x² -17x + 60
       f'(x) = 3x² -8x  -17
    """
    # polynomial_derivative([1, -4, -17, 60]) → [3, -8, -17]
    n = len(coefficients)
    powers = reversed(range(1, n))
    return list(map(operator.mul, coefficients, powers))

def sieve(n):
    "Primes less than n."
    # sieve(30) → 2 3 5 7 11 13 17 19 23 29
    if n > 2:
        yield 2
    data = bytearray((0, 1)) * (n // 2)
    for p in iter_index(data, 1, start=3, stop=math.isqrt(n) + 1):
        data[p*p : n : p+p] = bytes(len(range(p*p, n, p+p)))
    yield from iter_index(data, 1, start=3)

def factor(n):
    "Prime factors of n."
    # factor(99) → 3 3 11
    # factor(1_000_000_000_000_007) → 47 59 360620266859
    # factor(1_000_000_000_000_403) → 1000000000000403
    for prime in sieve(math.isqrt(n) + 1):
        while not n % prime:
            yield prime
            n //= prime
            if n == 1:
                return
    if n > 1:
        yield n

def totient(n):
    "Count of natural numbers up to n that are coprime to n."
    # https://mathworld.wolfram.com/TotientFunction.html
    # totient(12) → 4 because len([1, 5, 7, 11]) == 4
    for prime in set(factor(n)):
        n -= n // prime
    return n