Spec-Zone.ru › Python 3.12

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

Этот модуль реализует набор строительных блоков для итераторов, созданных по мотивам конструкций из 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()

[start[, step]]

start, start+step, start+2*step, …

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

cycle()

p

p0, p1, … plast, 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()

p, n

(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()

iterable

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

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

compress()

data, selectors

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

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

dropwhile()

predicate, seq

seq[n], seq[n+1], начиная с момента, когда предикат перестаёт выполняться

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

filterfalse()

predicate, seq

элементы seq, для которых predicate(elem) не выполняется

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

groupby()

iterable[, key]

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

islice()

seq, [start,] stop [, step]

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

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

pairwise()

iterable

(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()

predicate, seq

seq[0], seq[1], … до тех пор, пока предикат выполняется

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

tee()

it, n

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

permutations()

p[, r]

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

combinations()

p, r

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

combinations_with_replacement()

p, r

кортежи длины 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

Функции itertools

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

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

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

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

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

Приблизительный эквивалент:

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() — текущее произведение. С помощью накопления процентов и применения платежей можно составить графики погашения кредита:

>>> 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() — аналогичную функцию, возвращающую только итоговое накопленное значение.

Добавлено в версии 3.2.

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

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

itertools.batched(iterable, n)

Разбивает данные из iterable на кортежи длины n. Последняя группа может быть короче 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):
    # 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)):
        yield batch

Добавлено в версии 3.12.

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(), содержащую только элементы, являющиеся подпоследовательностями iterable. Длина результата определяется функцией 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(), содержащую только элементы, являющиеся подпоследовательностями iterable (с возможными повторениями). Число возвращаемых подпоследовательностей равно (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)

Добавлено в версии 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)

Добавлено в версии 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: Добавлен аргумент step и разрешены аргументы нецелого типа.

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 может потребоваться значительный объём дополнительной памяти (в зависимости от длины итерируемого объекта).

itertools.dropwhile(predicate, iterable)

Создаёт итератор, отбрасывающий элементы из iterable, пока predicate возвращает true, а затем возвращающий все оставшиеся элементы. Приблизительный эквивалент:

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

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

itertools.filterfalse(predicate, iterable)

Создаёт итератор, фильтрующий элементы из iterable и возвращающий только те, для которых predicate возвращает ложное значение. Если predicate равен 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.

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

Приблизительный эквивалент:

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

Добавлено в версии 3.10.

itertools.permutations(iterable, r=None)

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

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

Результат представляет собой подпоследовательность 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])

Создаёт итератор, бесконечно возвращающий object. Работает бесконечно, если не задан аргумент 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)

Создаёт итератор, вычисляющий function с аргументами, полученными из 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, пока predicate возвращает true. Приблизительный эквивалент:

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-itertools before_and_after().

itertools.tee(iterable, n=2)

Возвращает n независимых итераторов из одного итерируемого объекта.

Приблизительный эквивалент:

def tee(iterable, n=2):
    if n < 0:
        raise ValueError
    if n == 0:
        return ()
    iterator = _tee(iterable)
    result = [iterator]
    for _ in range(n - 1):
        result.append(_tee(iterator))
    return tuple(result)

class _tee:

    def __init__(self, iterable):
        it = iter(iterable)
        if isinstance(it, _tee):
            self.iterator = it.iterator
            self.link = it.link
        else:
            self.iterator = it
            self.link = [None, None]

    def __iter__(self):
        return self

    def __next__(self):
        link = self.link
        if link[1] is None:
            link[0] = next(self.iterator)
            link[1] = [None, None]
        value, self.link = link
        return value

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

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

itertools.zip_longest(*iterables, fillvalue=None)

Создаёт итератор, объединяющий элементы каждого из iterables.

Если длины итерируемых объектов различаются, недостающие значения заполняются значением 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.

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

Вторая цель примеров — служить инкубатором. Итераторы itertools accumulate(), compress() и pairwise() изначально появились в виде примеров. Сейчас проверяется полезность примеров sliding_window(), iter_index() и sieve().

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

python -m pip install more-itertools

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

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 loops(n):
    "Loop n times. Like range(n) but without creating integers."
    # for _ in loops(100): ...
    return repeat(None, 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)

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 is_prime(n):
    "Return True if n is prime."
    # is_prime(1_000_000_000_000_403) → True
    return n > 1 and all(n % p for p in sieve(math.isqrt(n) + 1))

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

def multinomial(*counts):
    "Number of distinct arrangements of a multiset."
    # Counter('abracadabra').values() -> 5 2 1 1 2
    # multinomial(5, 2, 1, 1, 2) → 83160
    return math.prod(map(math.comb, accumulate(counts), counts))

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/itertools.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API