itertools — Функции для создания итераторов, обеспечивающих эффективный перебор
Этот модуль реализует ряд строительных блоков для создания итераторов, вдохновлённых конструкциями из APL, Haskell и SML. Каждый из них был преобразован в форму, подходящую для Python.
Модуль стандартизирует базовый набор быстрых и эффективных по памяти инструментов, полезных как по отдельности, так и в сочетании друг с другом. Вместе они образуют «алгебру итераторов», позволяющую кратко и эффективно создавать специализированные инструменты на чистом Python.
Например, в SML есть инструмент табуляции: tabulate(f), который создаёт последовательность f(0), f(1), .... Того же эффекта можно добиться в Python, объединив map() и count(), чтобы получить map(f, count()).
Бесконечные итераторы:
Итератор | Аргументы | Результаты | Пример |
|---|---|---|---|
[start[, step]] | start, start+step, start+2*step, … |
| |
p | p0, p1, … plast, p0, p1, … |
| |
elem [,n] | elem, elem, elem, … бесконечно или до n раз |
|
Итераторы, завершающиеся по окончании самой короткой входной последовательности:
Итератор | Аргументы | Результаты | Пример |
|---|---|---|---|
p [,func] | p0, p0+p1, p0+p1+p2, … |
| |
p, n | (p0, p1, …, p_n-1), … |
| |
p, q, … | p0, p1, … plast, q0, q1, … |
| |
iterable | p0, p1, … plast, q0, q1, … |
| |
data, selectors | (d[0] if s[0]), (d[1] if s[1]), … |
| |
predicate, seq | seq[n], seq[n+1], начиная с момента, когда предикат перестаёт выполняться |
| |
predicate, seq | элементы seq, для которых predicate(elem) не выполняется |
| |
iterable[, key] | подитераторы, сгруппированные по значению key(v) |
| |
seq, [start,] stop [, step] | элементы из seq[start:stop:step] |
| |
iterable | (p[0], p[1]), (p[1], p[2]) |
| |
func, seq | func(*seq[0]), func(*seq[1]), … |
| |
predicate, seq | seq[0], seq[1], пока предикат выполняется |
| |
it, n | it1, it2, … itn разбивает один итератор на n | ||
p, q, … | (p[0], q[0]), (p[1], q[1]), … |
|
Комбинаторные итераторы:
Итератор | Аргументы | Результаты |
|---|---|---|
p, q, … [repeat=1] | декартово произведение, эквивалентное вложенным циклам for | |
p[, r] | кортежи длины r, все возможные упорядочивания без повторяющихся элементов | |
p, r | кортежи длины r в отсортированном порядке без повторяющихся элементов | |
p, r | кортежи длины r в отсортированном порядке с повторяющимися элементами |
Примеры | Результаты |
|---|---|
|
|
|
|
|
|
|
|
Функции 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, *, 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', 2) → AB CD EF 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Добавлено в версии 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(), в которой оставлены только элементы, являющиеся подпоследовательностями 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, возвращает элементы, имеющие значение false. Примерно эквивалентно: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Если на входе итератор, то полное потребление islice продвигает входной итератор на
max(start, stop)шагов независимо от значения 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 if repeat < 0: raise ValueError('repeat argument cannot be negative') 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Если входной iterable уже является объектом итератора tee, все элементы возвращаемого кортежа создаются так, как если бы они были получены в результате вышестоящего вызова
tee(). Этот «этап уплощения» позволяет вложенным вызовамtee()совместно использовать одну и ту же базовую цепочку данных и выполнять одно обновление вместо цепочки вызовов.Свойство уплощения позволяет эффективно заглядывать в итераторы tee:
def lookahead(tee_iterator): "Return the next value without moving the input forward" [forked_iterator] = tee(tee_iterator, 1) return next(forked_iterator)>>> iterator = iter('abcdef') >>> [iterator] = tee(iterator, 1) # Make the input peekable >>> next(iterator) # Move the iterator forward 'a' >>> lookahead(iterator) # Check next value 'b' >>> next(iterator) # Continue moving forward 'b'Итераторы
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.
Основная цель рецептов 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 и генераторам, создающим дополнительные накладные расходы интерпретатора.
from collections import Counter, deque
from contextlib import suppress
from functools import reduce
from math import comb, prod, sumprod, isqrt
from operator import itemgetter, getitem, mul, neg
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(function, times=None, *args):
"Repeat calls to a function with specified arguments."
if times is None:
return starmap(function, repeat(args))
return starmap(function, 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(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:
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(itemgetter(0), groupby(iterable))
return map(next, map(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]
sequenced = sorted(iterable, key=key, reverse=reverse)
return unique_justseen(sequenced, 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 = 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 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(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 suppress(ValueError):
while True:
yield (i := seq_index(value, i, stop))
i += 1
def iter_except(function, exception, first=None):
"Convert a call-until-exception interface to an iterator interface."
# iter_except(d.popitem, KeyError) → non-blocking dictionary iterator
with suppress(exception):
if first is not None:
yield first()
while True:
yield function()
Следующие рецепты имеют более выраженный математический характер:
def multinomial(*counts):
"Number of distinct arrangements of a multiset."
# Counter('abracadabra').values() → 5 2 2 1 1
# multinomial(5, 2, 2, 1, 1) → 83160
return prod(map(comb, accumulate(counts), counts))
def powerset(iterable):
"Subsequences of the iterable from shortest to longest."
# 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 sumprod(*tee(iterable))
def reshape(matrix, columns):
"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), columns, 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(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(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(neg, roots))
return list(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 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(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=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(isqrt(n) + 1):
while not n % prime:
yield prime
n //= prime
if n == 1:
return
if n > 1:
yield n
def is_prime(n):
"Return True if n is prime."
# is_prime(1_000_000_000_000_403) → True
return n > 1 and next(factor(n)) == 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
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/itertools.html