mirror of https://github.com/python/cpython
1843 lines
65 KiB
ReStructuredText
1843 lines
65 KiB
ReStructuredText
:mod:`!itertools` --- Functions creating iterators for efficient looping
|
||
========================================================================
|
||
|
||
.. module:: itertools
|
||
:synopsis: Functions creating iterators for efficient looping.
|
||
|
||
.. moduleauthor:: Raymond Hettinger <python@rcn.com>
|
||
.. sectionauthor:: Raymond Hettinger <python@rcn.com>
|
||
|
||
.. testsetup::
|
||
|
||
from itertools import *
|
||
import collections
|
||
import math
|
||
import operator
|
||
import random
|
||
|
||
--------------
|
||
|
||
This module implements a number of :term:`iterator` building blocks inspired
|
||
by constructs from APL, Haskell, and SML. Each has been recast in a form
|
||
suitable for Python.
|
||
|
||
The module standardizes a core set of fast, memory efficient tools that are
|
||
useful by themselves or in combination. Together, they form an "iterator
|
||
algebra" making it possible to construct specialized tools succinctly and
|
||
efficiently in pure Python.
|
||
|
||
For instance, SML provides a tabulation tool: ``tabulate(f)`` which produces a
|
||
sequence ``f(0), f(1), ...``. The same effect can be achieved in Python
|
||
by combining :func:`map` and :func:`count` to form ``map(f, count())``.
|
||
|
||
These tools and their built-in counterparts also work well with the high-speed
|
||
functions in the :mod:`operator` module. For example, the multiplication
|
||
operator can be mapped across two vectors to form an efficient dot-product:
|
||
``sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))``.
|
||
|
||
|
||
**Infinite iterators:**
|
||
|
||
================== ================= ================================================= =========================================
|
||
Iterator Arguments Results Example
|
||
================== ================= ================================================= =========================================
|
||
:func:`count` [start[, step]] start, start+step, start+2*step, ... ``count(10) → 10 11 12 13 14 ...``
|
||
:func:`cycle` p p0, p1, ... plast, p0, p1, ... ``cycle('ABCD') → A B C D A B C D ...``
|
||
:func:`repeat` elem [,n] elem, elem, elem, ... endlessly or up to n times ``repeat(10, 3) → 10 10 10``
|
||
================== ================= ================================================= =========================================
|
||
|
||
**Iterators terminating on the shortest input sequence:**
|
||
|
||
============================ ============================ ================================================= =============================================================
|
||
Iterator Arguments Results Example
|
||
============================ ============================ ================================================= =============================================================
|
||
:func:`accumulate` p [,func] p0, p0+p1, p0+p1+p2, ... ``accumulate([1,2,3,4,5]) → 1 3 6 10 15``
|
||
:func:`batched` p, n (p0, p1, ..., p_n-1), ... ``batched('ABCDEFG', n=3) → ABC DEF G``
|
||
:func:`chain` p, q, ... p0, p1, ... plast, q0, q1, ... ``chain('ABC', 'DEF') → A B C D E F``
|
||
:func:`chain.from_iterable` iterable p0, p1, ... plast, q0, q1, ... ``chain.from_iterable(['ABC', 'DEF']) → A B C D E F``
|
||
:func:`compress` data, selectors (d[0] if s[0]), (d[1] if s[1]), ... ``compress('ABCDEF', [1,0,1,0,1,1]) → A C E F``
|
||
:func:`dropwhile` predicate, seq seq[n], seq[n+1], starting when predicate fails ``dropwhile(lambda x: x<5, [1,4,6,3,8]) → 6 3 8``
|
||
:func:`filterfalse` predicate, seq elements of seq where predicate(elem) fails ``filterfalse(lambda x: x<5, [1,4,6,3,8]) → 6 8``
|
||
:func:`groupby` iterable[, key] sub-iterators grouped by value of key(v) ``groupby(['A','B','DEF'], len) → (1, A B) (3, DEF)``
|
||
:func:`islice` seq, [start,] stop [, step] elements from seq[start:stop:step] ``islice('ABCDEFG', 2, None) → C D E F G``
|
||
:func:`pairwise` iterable (p[0], p[1]), (p[1], p[2]) ``pairwise('ABCDEFG') → AB BC CD DE EF FG``
|
||
:func:`starmap` func, seq func(\*seq[0]), func(\*seq[1]), ... ``starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000``
|
||
:func:`takewhile` predicate, seq seq[0], seq[1], until predicate fails ``takewhile(lambda x: x<5, [1,4,6,3,8]) → 1 4``
|
||
:func:`tee` it, n it1, it2, ... itn splits one iterator into n
|
||
:func:`zip_longest` p, q, ... (p[0], q[0]), (p[1], q[1]), ... ``zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-``
|
||
============================ ============================ ================================================= =============================================================
|
||
|
||
**Combinatoric iterators:**
|
||
|
||
============================================== ==================== =============================================================
|
||
Iterator Arguments Results
|
||
============================================== ==================== =============================================================
|
||
:func:`product` p, q, ... [repeat=1] cartesian product, equivalent to a nested for-loop
|
||
:func:`permutations` p[, r] r-length tuples, all possible orderings, no repeated elements
|
||
:func:`combinations` p, r r-length tuples, in sorted order, no repeated elements
|
||
:func:`combinations_with_replacement` p, r r-length tuples, in sorted order, with repeated elements
|
||
============================================== ==================== =============================================================
|
||
|
||
============================================== =============================================================
|
||
Examples Results
|
||
============================================== =============================================================
|
||
``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-functions:
|
||
|
||
Itertool Functions
|
||
------------------
|
||
|
||
The following functions all construct and return iterators. Some provide
|
||
streams of infinite length, so they should only be accessed by functions or
|
||
loops that truncate the stream.
|
||
|
||
|
||
.. function:: accumulate(iterable[, function, *, initial=None])
|
||
|
||
Make an iterator that returns accumulated sums or accumulated
|
||
results from other binary functions.
|
||
|
||
The *function* defaults to addition. The *function* should accept
|
||
two arguments, an accumulated total and a value from the *iterable*.
|
||
|
||
If an *initial* value is provided, the accumulation will start with
|
||
that value and the output will have one more element than the input
|
||
iterable.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
To compute a running minimum, set *function* to :func:`min`.
|
||
For a running maximum, set *function* to :func:`max`.
|
||
Or for a running product, set *function* to :func:`operator.mul`.
|
||
To build an `amortization table
|
||
<https://www.ramseysolutions.com/real-estate/amortization-schedule>`_,
|
||
accumulate the interest and apply payments:
|
||
|
||
.. doctest::
|
||
|
||
>>> 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]
|
||
|
||
See :func:`functools.reduce` for a similar function that returns only the
|
||
final accumulated value.
|
||
|
||
.. versionadded:: 3.2
|
||
|
||
.. versionchanged:: 3.3
|
||
Added the optional *function* parameter.
|
||
|
||
.. versionchanged:: 3.8
|
||
Added the optional *initial* parameter.
|
||
|
||
|
||
.. function:: batched(iterable, n, *, strict=False)
|
||
|
||
Batch data from the *iterable* into tuples of length *n*. The last
|
||
batch may be shorter than *n*.
|
||
|
||
If *strict* is true, will raise a :exc:`ValueError` if the final
|
||
batch is shorter than *n*.
|
||
|
||
Loops over the input iterable and accumulates data into tuples up to
|
||
size *n*. The input is consumed lazily, just enough to fill a batch.
|
||
The result is yielded as soon as the batch is full or when the input
|
||
iterable is exhausted:
|
||
|
||
.. doctest::
|
||
|
||
>>> flattened_data = ['roses', 'red', 'violets', 'blue', 'sugar', 'sweet']
|
||
>>> unflattened = list(batched(flattened_data, 2))
|
||
>>> unflattened
|
||
[('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
.. versionadded:: 3.12
|
||
|
||
.. versionchanged:: 3.13
|
||
Added the *strict* option.
|
||
|
||
|
||
.. function:: chain(*iterables)
|
||
|
||
Make an iterator that returns elements from the first iterable until
|
||
it is exhausted, then proceeds to the next iterable, until all of the
|
||
iterables are exhausted. This combines multiple data sources into a
|
||
single iterator. Roughly equivalent to::
|
||
|
||
def chain(*iterables):
|
||
# chain('ABC', 'DEF') → A B C D E F
|
||
for iterable in iterables:
|
||
yield from iterable
|
||
|
||
|
||
.. classmethod:: chain.from_iterable(iterable)
|
||
|
||
Alternate constructor for :func:`chain`. Gets chained inputs from a
|
||
single iterable argument that is evaluated lazily. Roughly equivalent to::
|
||
|
||
def from_iterable(iterables):
|
||
# chain.from_iterable(['ABC', 'DEF']) → A B C D E F
|
||
for iterable in iterables:
|
||
yield from iterable
|
||
|
||
|
||
.. function:: combinations(iterable, r)
|
||
|
||
Return *r* length subsequences of elements from the input *iterable*.
|
||
|
||
The output is a subsequence of :func:`product` keeping only entries that
|
||
are subsequences of the *iterable*. The length of the output is given
|
||
by :func:`math.comb` which computes ``n! / r! / (n - r)!`` when ``0 ≤ r
|
||
≤ n`` or zero when ``r > n``.
|
||
|
||
The combination tuples are emitted in lexicographic order according to
|
||
the order of the input *iterable*. If the input *iterable* is sorted,
|
||
the output tuples will be produced in sorted order.
|
||
|
||
Elements are treated as unique based on their position, not on their
|
||
value. If the input elements are unique, there will be no repeated
|
||
values within each combination.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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)
|
||
|
||
|
||
.. function:: combinations_with_replacement(iterable, r)
|
||
|
||
Return *r* length subsequences of elements from the input *iterable*
|
||
allowing individual elements to be repeated more than once.
|
||
|
||
The output is a subsequence of :func:`product` that keeps only entries
|
||
that are subsequences (with possible repeated elements) of the
|
||
*iterable*. The number of subsequence returned is ``(n + r - 1)! / r! /
|
||
(n - 1)!`` when ``n > 0``.
|
||
|
||
The combination tuples are emitted in lexicographic order according to
|
||
the order of the input *iterable*. if the input *iterable* is sorted,
|
||
the output tuples will be produced in sorted order.
|
||
|
||
Elements are treated as unique based on their position, not on their
|
||
value. If the input elements are unique, the generated combinations
|
||
will also be unique.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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)
|
||
|
||
.. versionadded:: 3.1
|
||
|
||
|
||
.. function:: compress(data, selectors)
|
||
|
||
Make an iterator that returns elements from *data* where the
|
||
corresponding element in *selectors* is true. Stops when either the
|
||
*data* or *selectors* iterables have been exhausted. Roughly
|
||
equivalent to::
|
||
|
||
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)
|
||
|
||
.. versionadded:: 3.1
|
||
|
||
|
||
.. function:: count(start=0, step=1)
|
||
|
||
Make an iterator that returns evenly spaced values beginning with
|
||
*start*. Can be used with :func:`map` to generate consecutive data
|
||
points or with :func:`zip` to add sequence numbers. Roughly
|
||
equivalent to::
|
||
|
||
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
|
||
|
||
When counting with floating-point numbers, better accuracy can sometimes be
|
||
achieved by substituting multiplicative code such as: ``(start + step * i
|
||
for i in count())``.
|
||
|
||
.. versionchanged:: 3.1
|
||
Added *step* argument and allowed non-integer arguments.
|
||
|
||
|
||
.. function:: cycle(iterable)
|
||
|
||
Make an iterator returning elements from the *iterable* and saving a
|
||
copy of each. When the iterable is exhausted, return elements from
|
||
the saved copy. Repeats indefinitely. Roughly equivalent to::
|
||
|
||
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
|
||
|
||
This itertool may require significant auxiliary storage (depending on
|
||
the length of the iterable).
|
||
|
||
|
||
.. function:: dropwhile(predicate, iterable)
|
||
|
||
Make an iterator that drops elements from the *iterable* while the
|
||
*predicate* is true and afterwards returns every element. Roughly
|
||
equivalent to::
|
||
|
||
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
|
||
|
||
Note this does not produce *any* output until the predicate first
|
||
becomes false, so this itertool may have a lengthy start-up time.
|
||
|
||
|
||
.. function:: filterfalse(predicate, iterable)
|
||
|
||
Make an iterator that filters elements from the *iterable* returning
|
||
only those for which the *predicate* returns a false value. If
|
||
*predicate* is ``None``, returns the items that are false. Roughly
|
||
equivalent to::
|
||
|
||
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
|
||
|
||
|
||
.. function:: groupby(iterable, key=None)
|
||
|
||
Make an iterator that returns consecutive keys and groups from the *iterable*.
|
||
The *key* is a function computing a key value for each element. If not
|
||
specified or is ``None``, *key* defaults to an identity function and returns
|
||
the element unchanged. Generally, the iterable needs to already be sorted on
|
||
the same key function.
|
||
|
||
The operation of :func:`groupby` is similar to the ``uniq`` filter in Unix. It
|
||
generates a break or new group every time the value of the key function changes
|
||
(which is why it is usually necessary to have sorted the data using the same key
|
||
function). That behavior differs from SQL's GROUP BY which aggregates common
|
||
elements regardless of their input order.
|
||
|
||
The returned group is itself an iterator that shares the underlying iterable
|
||
with :func:`groupby`. Because the source is shared, when the :func:`groupby`
|
||
object is advanced, the previous group is no longer visible. So, if that data
|
||
is needed later, it should be stored as a list::
|
||
|
||
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)
|
||
|
||
:func:`groupby` is roughly equivalent to::
|
||
|
||
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
|
||
|
||
|
||
.. function:: islice(iterable, stop)
|
||
islice(iterable, start, stop[, step])
|
||
|
||
Make an iterator that returns selected elements from the iterable.
|
||
Works like sequence slicing but does not support negative values for
|
||
*start*, *stop*, or *step*.
|
||
|
||
If *start* is zero or ``None``, iteration starts at zero. Otherwise,
|
||
elements from the iterable are skipped until *start* is reached.
|
||
|
||
If *stop* is ``None``, iteration continues until the input is
|
||
exhausted, if at all. Otherwise, it stops at the specified position.
|
||
|
||
If *step* is ``None``, the step defaults to one. Elements are returned
|
||
consecutively unless *step* is set higher than one which results in
|
||
items being skipped.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
If the input is an iterator, then fully consuming the *islice*
|
||
advances the input iterator by ``max(start, stop)`` steps regardless
|
||
of the *step* value.
|
||
|
||
|
||
.. function:: pairwise(iterable)
|
||
|
||
Return successive overlapping pairs taken from the input *iterable*.
|
||
|
||
The number of 2-tuples in the output iterator will be one fewer than the
|
||
number of inputs. It will be empty if the input iterable has fewer than
|
||
two values.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
.. versionadded:: 3.10
|
||
|
||
|
||
.. function:: permutations(iterable, r=None)
|
||
|
||
Return successive *r* length `permutations of elements
|
||
<https://www.britannica.com/science/permutation>`_ from the *iterable*.
|
||
|
||
If *r* is not specified or is ``None``, then *r* defaults to the length
|
||
of the *iterable* and all possible full-length permutations
|
||
are generated.
|
||
|
||
The output is a subsequence of :func:`product` where entries with
|
||
repeated elements have been filtered out. The length of the output is
|
||
given by :func:`math.perm` which computes ``n! / (n - r)!`` when
|
||
``0 ≤ r ≤ n`` or zero when ``r > n``.
|
||
|
||
The permutation tuples are emitted in lexicographic order according to
|
||
the order of the input *iterable*. If the input *iterable* is sorted,
|
||
the output tuples will be produced in sorted order.
|
||
|
||
Elements are treated as unique based on their position, not on their
|
||
value. If the input elements are unique, there will be no repeated
|
||
values within a permutation.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
|
||
.. function:: product(*iterables, repeat=1)
|
||
|
||
`Cartesian product <https://en.wikipedia.org/wiki/Cartesian_product>`_
|
||
of the input iterables.
|
||
|
||
Roughly equivalent to nested for-loops in a generator expression. For example,
|
||
``product(A, B)`` returns the same as ``((x,y) for x in A for y in B)``.
|
||
|
||
The nested loops cycle like an odometer with the rightmost element advancing
|
||
on every iteration. This pattern creates a lexicographic ordering so that if
|
||
the input's iterables are sorted, the product tuples are emitted in sorted
|
||
order.
|
||
|
||
To compute the product of an iterable with itself, specify the number of
|
||
repetitions with the optional *repeat* keyword argument. For example,
|
||
``product(A, repeat=4)`` means the same as ``product(A, A, A, A)``.
|
||
|
||
This function is roughly equivalent to the following code, except that the
|
||
actual implementation does not build up intermediate results in memory::
|
||
|
||
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)
|
||
|
||
Before :func:`product` runs, it completely consumes the input iterables,
|
||
keeping pools of values in memory to generate the products. Accordingly,
|
||
it is only useful with finite inputs.
|
||
|
||
|
||
.. function:: repeat(object[, times])
|
||
|
||
Make an iterator that returns *object* over and over again. Runs indefinitely
|
||
unless the *times* argument is specified.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
A common use for *repeat* is to supply a stream of constant values to *map*
|
||
or *zip*:
|
||
|
||
.. doctest::
|
||
|
||
>>> list(map(pow, range(10), repeat(2)))
|
||
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
||
|
||
|
||
.. function:: starmap(function, iterable)
|
||
|
||
Make an iterator that computes the *function* using arguments obtained
|
||
from the *iterable*. Used instead of :func:`map` when argument
|
||
parameters have already been "pre-zipped" into tuples.
|
||
|
||
The difference between :func:`map` and :func:`starmap` parallels the
|
||
distinction between ``function(a,b)`` and ``function(*c)``. Roughly
|
||
equivalent to::
|
||
|
||
def starmap(function, iterable):
|
||
# starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000
|
||
for args in iterable:
|
||
yield function(*args)
|
||
|
||
|
||
.. function:: takewhile(predicate, iterable)
|
||
|
||
Make an iterator that returns elements from the *iterable* as long as
|
||
the *predicate* is true. Roughly equivalent to::
|
||
|
||
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
|
||
|
||
Note, the element that first fails the predicate condition is
|
||
consumed from the input iterator and there is no way to access it.
|
||
This could be an issue if an application wants to further consume the
|
||
input iterator after *takewhile* has been run to exhaustion. To work
|
||
around this problem, consider using `more-iterools before_and_after()
|
||
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.before_and_after>`_
|
||
instead.
|
||
|
||
|
||
.. function:: tee(iterable, n=2)
|
||
|
||
Return *n* independent iterators from a single iterable.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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
|
||
|
||
When the input *iterable* is already a tee iterator object, all
|
||
members of the return tuple are constructed as if they had been
|
||
produced by the upstream :func:`tee` call. This "flattening step"
|
||
allows nested :func:`tee` calls to share the same underlying data
|
||
chain and to have a single update step rather than a chain of calls.
|
||
|
||
The flattening property makes tee iterators efficiently peekable:
|
||
|
||
.. testcode::
|
||
|
||
def lookahead(tee_iterator):
|
||
"Return the next value without moving the input forward"
|
||
[forked_iterator] = tee(tee_iterator, 1)
|
||
return next(forked_iterator)
|
||
|
||
.. doctest::
|
||
|
||
>>> 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`` iterators are not threadsafe. A :exc:`RuntimeError` may be
|
||
raised when simultaneously using iterators returned by the same :func:`tee`
|
||
call, even if the original *iterable* is threadsafe.
|
||
|
||
This itertool may require significant auxiliary storage (depending on how
|
||
much temporary data needs to be stored). In general, if one iterator uses
|
||
most or all of the data before another iterator starts, it is faster to use
|
||
:func:`list` instead of :func:`tee`.
|
||
|
||
|
||
.. function:: zip_longest(*iterables, fillvalue=None)
|
||
|
||
Make an iterator that aggregates elements from each of the
|
||
*iterables*.
|
||
|
||
If the iterables are of uneven length, missing values are filled-in
|
||
with *fillvalue*. If not specified, *fillvalue* defaults to ``None``.
|
||
|
||
Iteration continues until the longest iterable is exhausted.
|
||
|
||
Roughly equivalent to::
|
||
|
||
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)
|
||
|
||
If one of the iterables is potentially infinite, then the :func:`zip_longest`
|
||
function should be wrapped with something that limits the number of calls
|
||
(for example :func:`islice` or :func:`takewhile`).
|
||
|
||
|
||
.. _itertools-recipes:
|
||
|
||
Itertools Recipes
|
||
-----------------
|
||
|
||
This section shows recipes for creating an extended toolset using the existing
|
||
itertools as building blocks.
|
||
|
||
The primary purpose of the itertools recipes is educational. The recipes show
|
||
various ways of thinking about individual tools — for example, that
|
||
``chain.from_iterable`` is related to the concept of flattening. The recipes
|
||
also give ideas about ways that the tools can be combined — for example, how
|
||
``starmap()`` and ``repeat()`` can work together. The recipes also show patterns
|
||
for using itertools with the :mod:`operator` and :mod:`collections` modules as
|
||
well as with the built-in itertools such as ``map()``, ``filter()``,
|
||
``reversed()``, and ``enumerate()``.
|
||
|
||
A secondary purpose of the recipes is to serve as an incubator. The
|
||
``accumulate()``, ``compress()``, and ``pairwise()`` itertools started out as
|
||
recipes. Currently, the ``sliding_window()``, ``iter_index()``, and ``sieve()``
|
||
recipes are being tested to see whether they prove their worth.
|
||
|
||
Substantially all of these recipes and many, many others can be installed from
|
||
the :pypi:`more-itertools` project found
|
||
on the Python Package Index::
|
||
|
||
python -m pip install more-itertools
|
||
|
||
Many of the recipes offer the same high performance as the underlying toolset.
|
||
Superior memory performance is kept by processing elements one at a time rather
|
||
than bringing the whole iterable into memory all at once. Code volume is kept
|
||
small by linking the tools together in a `functional style
|
||
<https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`_. High speed
|
||
is retained by preferring "vectorized" building blocks over the use of for-loops
|
||
and :term:`generators <generator>` which incur interpreter overhead.
|
||
|
||
.. testcode::
|
||
|
||
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 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()
|
||
|
||
|
||
The following recipes have a more mathematical flavor:
|
||
|
||
.. testcode::
|
||
|
||
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
|
||
|
||
|
||
.. doctest::
|
||
:hide:
|
||
|
||
These examples no longer appear in the docs but are guaranteed
|
||
to keep working.
|
||
|
||
>>> amounts = [120.15, 764.05, 823.14]
|
||
>>> for checknum, amount in zip(count(1200), amounts):
|
||
... print('Check %d is for $%.2f' % (checknum, amount))
|
||
...
|
||
Check 1200 is for $120.15
|
||
Check 1201 is for $764.05
|
||
Check 1202 is for $823.14
|
||
|
||
>>> import operator
|
||
>>> for cube in map(operator.pow, range(1,4), repeat(3)):
|
||
... print(cube)
|
||
...
|
||
1
|
||
8
|
||
27
|
||
|
||
>>> reportlines = ['EuroPython', 'Roster', '', 'alex', '', 'laura', '', 'martin', '', 'walter', '', 'samuele']
|
||
>>> for name in islice(reportlines, 3, None, 2):
|
||
... print(name.title())
|
||
...
|
||
Alex
|
||
Laura
|
||
Martin
|
||
Walter
|
||
Samuele
|
||
|
||
>>> from operator import itemgetter
|
||
>>> d = dict(a=1, b=2, c=1, d=2, e=1, f=2, g=3)
|
||
>>> di = sorted(sorted(d.items()), key=itemgetter(1))
|
||
>>> for k, g in groupby(di, itemgetter(1)):
|
||
... print(k, list(map(itemgetter(0), g)))
|
||
...
|
||
1 ['a', 'c', 'e']
|
||
2 ['b', 'd', 'f']
|
||
3 ['g']
|
||
|
||
# Find runs of consecutive numbers using groupby. The key to the solution
|
||
# is differencing with a range so that consecutive numbers all appear in
|
||
# same group.
|
||
>>> data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
|
||
>>> for k, g in groupby(enumerate(data), lambda t:t[0]-t[1]):
|
||
... print(list(map(operator.itemgetter(1), g)))
|
||
...
|
||
[1]
|
||
[4, 5, 6]
|
||
[10]
|
||
[15, 16, 17, 18]
|
||
[22]
|
||
[25, 26, 27, 28]
|
||
|
||
Now, we test all of the itertool recipes
|
||
|
||
>>> take(10, count())
|
||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
>>> # Verify that the input is consumed lazily
|
||
>>> it = iter('abcdef')
|
||
>>> take(3, it)
|
||
['a', 'b', 'c']
|
||
>>> list(it)
|
||
['d', 'e', 'f']
|
||
|
||
|
||
>>> list(prepend(1, [2, 3, 4]))
|
||
[1, 2, 3, 4]
|
||
|
||
|
||
>>> list(enumerate('abc'))
|
||
[(0, 'a'), (1, 'b'), (2, 'c')]
|
||
|
||
|
||
>>> list(islice(tabulate(lambda x: 2*x), 4))
|
||
[0, 2, 4, 6]
|
||
|
||
|
||
>>> list(tail(3, 'ABCDEFG'))
|
||
['E', 'F', 'G']
|
||
>>> # Verify the input is consumed greedily
|
||
>>> input_iterator = iter('ABCDEFG')
|
||
>>> output_iterator = tail(3, input_iterator)
|
||
>>> list(input_iterator)
|
||
[]
|
||
|
||
|
||
>>> it = iter(range(10))
|
||
>>> consume(it, 3)
|
||
>>> # Verify the input is consumed lazily
|
||
>>> next(it)
|
||
3
|
||
>>> # Verify the input is consumed completely
|
||
>>> consume(it)
|
||
>>> next(it, 'Done')
|
||
'Done'
|
||
|
||
|
||
>>> nth('abcde', 3)
|
||
'd'
|
||
>>> nth('abcde', 9) is None
|
||
True
|
||
>>> # Verify that the input is consumed lazily
|
||
>>> it = iter('abcde')
|
||
>>> nth(it, 2)
|
||
'c'
|
||
>>> list(it)
|
||
['d', 'e']
|
||
|
||
|
||
>>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')]
|
||
[True, True, True, False, False]
|
||
>>> [all_equal(s, key=str.casefold) for s in ('', 'A', 'AaAa', 'AAAB', 'AAABA')]
|
||
[True, True, True, False, False]
|
||
>>> # Verify that the input is consumed lazily and that only
|
||
>>> # one element of a second equivalence class is used to disprove
|
||
>>> # the assertion that all elements are equal.
|
||
>>> it = iter('aaabbbccc')
|
||
>>> all_equal(it)
|
||
False
|
||
>>> ''.join(it)
|
||
'bbccc'
|
||
|
||
|
||
>>> quantify(range(99), lambda x: x%2==0)
|
||
50
|
||
>>> quantify([True, False, False, True, True])
|
||
3
|
||
>>> quantify(range(12), predicate=lambda x: x%2==1)
|
||
6
|
||
|
||
|
||
>>> a = [[1, 2, 3], [4, 5, 6]]
|
||
>>> list(flatten(a))
|
||
[1, 2, 3, 4, 5, 6]
|
||
|
||
|
||
>>> list(ncycles('abc', 3))
|
||
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
|
||
>>> # Verify greedy consumption of input iterator
|
||
>>> input_iterator = iter('abc')
|
||
>>> output_iterator = ncycles(input_iterator, 3)
|
||
>>> list(input_iterator)
|
||
[]
|
||
|
||
|
||
>>> sum_of_squares([10, 20, 30])
|
||
1400
|
||
|
||
|
||
>>> list(reshape([(0, 1), (2, 3), (4, 5)], 3))
|
||
[(0, 1, 2), (3, 4, 5)]
|
||
>>> M = [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]
|
||
>>> list(reshape(M, 1))
|
||
[(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,), (10,), (11,)]
|
||
>>> list(reshape(M, 2))
|
||
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11)]
|
||
>>> list(reshape(M, 3))
|
||
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]
|
||
>>> list(reshape(M, 4))
|
||
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]
|
||
>>> list(reshape(M, 5))
|
||
Traceback (most recent call last):
|
||
...
|
||
ValueError: batched(): incomplete batch
|
||
>>> list(reshape(M, 6))
|
||
[(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)]
|
||
>>> list(reshape(M, 12))
|
||
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)]
|
||
|
||
|
||
>>> list(transpose([(1, 2, 3), (11, 22, 33)]))
|
||
[(1, 11), (2, 22), (3, 33)]
|
||
>>> # Verify that the inputs are consumed lazily
|
||
>>> input1 = iter([1, 2, 3])
|
||
>>> input2 = iter([11, 22, 33])
|
||
>>> output_iterator = transpose([input1, input2])
|
||
>>> next(output_iterator)
|
||
(1, 11)
|
||
>>> list(zip(input1, input2))
|
||
[(2, 22), (3, 33)]
|
||
|
||
|
||
>>> list(matmul([(7, 5), (3, 5)], [[2, 5], [7, 9]]))
|
||
[(49, 80), (41, 60)]
|
||
>>> list(matmul([[2, 5], [7, 9], [3, 4]], [[7, 11, 5, 4, 9], [3, 5, 2, 6, 3]]))
|
||
[(29, 47, 20, 38, 33), (76, 122, 53, 82, 90), (33, 53, 23, 36, 39)]
|
||
|
||
|
||
>>> list(convolve([1, -1, -20], [1, -3])) == [1, -4, -17, 60]
|
||
True
|
||
>>> data = [20, 40, 24, 32, 20, 28, 16]
|
||
>>> list(convolve(data, [0.25, 0.25, 0.25, 0.25]))
|
||
[5.0, 15.0, 21.0, 29.0, 29.0, 26.0, 24.0, 16.0, 11.0, 4.0]
|
||
>>> list(convolve(data, [1, -1]))
|
||
[20, 20, -16, 8, -12, 8, -12, -16]
|
||
>>> list(convolve(data, [1, -2, 1]))
|
||
[20, 0, -36, 24, -20, 20, -20, -4, 16]
|
||
>>> # Verify signal is consumed lazily and the kernel greedily
|
||
>>> signal_iterator = iter([10, 20, 30, 40, 50])
|
||
>>> kernel_iterator = iter([1, 2, 3])
|
||
>>> output_iterator = convolve(signal_iterator, kernel_iterator)
|
||
>>> list(kernel_iterator)
|
||
[]
|
||
>>> next(output_iterator)
|
||
10
|
||
>>> next(output_iterator)
|
||
40
|
||
>>> list(signal_iterator)
|
||
[30, 40, 50]
|
||
|
||
|
||
>>> from fractions import Fraction
|
||
>>> from decimal import Decimal
|
||
>>> polynomial_eval([1, -4, -17, 60], x=5)
|
||
0
|
||
>>> x = 5; x**3 - 4*x**2 -17*x + 60
|
||
0
|
||
>>> polynomial_eval([1, -4, -17, 60], x=2.5)
|
||
8.125
|
||
>>> x = 2.5; x**3 - 4*x**2 -17*x + 60
|
||
8.125
|
||
>>> polynomial_eval([1, -4, -17, 60], x=Fraction(2, 3))
|
||
Fraction(1274, 27)
|
||
>>> x = Fraction(2, 3); x**3 - 4*x**2 -17*x + 60
|
||
Fraction(1274, 27)
|
||
>>> polynomial_eval([1, -4, -17, 60], x=Decimal('1.75'))
|
||
Decimal('23.359375')
|
||
>>> x = Decimal('1.75'); x**3 - 4*x**2 -17*x + 60
|
||
Decimal('23.359375')
|
||
>>> polynomial_eval([], 2)
|
||
0
|
||
>>> polynomial_eval([], 2.5)
|
||
0.0
|
||
>>> polynomial_eval([], Fraction(2, 3))
|
||
Fraction(0, 1)
|
||
>>> polynomial_eval([], Decimal('1.75'))
|
||
Decimal('0')
|
||
>>> polynomial_eval([11], 7) == 11
|
||
True
|
||
>>> polynomial_eval([11, 2], 7) == 11 * 7 + 2
|
||
True
|
||
|
||
|
||
>>> polynomial_from_roots([5, -4, 3])
|
||
[1, -4, -17, 60]
|
||
>>> factored = lambda x: (x - 5) * (x + 4) * (x - 3)
|
||
>>> expanded = lambda x: x**3 -4*x**2 -17*x + 60
|
||
>>> all(factored(x) == expanded(x) for x in range(-10, 11))
|
||
True
|
||
|
||
|
||
>>> polynomial_derivative([1, -4, -17, 60])
|
||
[3, -8, -17]
|
||
|
||
|
||
>>> list(iter_index('AABCADEAF', 'A'))
|
||
[0, 1, 4, 7]
|
||
>>> list(iter_index('AABCADEAF', 'B'))
|
||
[2]
|
||
>>> list(iter_index('AABCADEAF', 'X'))
|
||
[]
|
||
>>> list(iter_index('', 'X'))
|
||
[]
|
||
>>> list(iter_index('AABCADEAF', 'A', 1))
|
||
[1, 4, 7]
|
||
>>> list(iter_index(iter('AABCADEAF'), 'A', 1))
|
||
[1, 4, 7]
|
||
>>> list(iter_index('AABCADEAF', 'A', 2))
|
||
[4, 7]
|
||
>>> list(iter_index(iter('AABCADEAF'), 'A', 2))
|
||
[4, 7]
|
||
>>> list(iter_index('AABCADEAF', 'A', 10))
|
||
[]
|
||
>>> list(iter_index(iter('AABCADEAF'), 'A', 10))
|
||
[]
|
||
>>> list(iter_index('AABCADEAF', 'A', 1, 7))
|
||
[1, 4]
|
||
>>> list(iter_index(iter('AABCADEAF'), 'A', 1, 7))
|
||
[1, 4]
|
||
>>> # Verify that ValueErrors not swallowed (gh-107208)
|
||
>>> def assert_no_value(iterable, forbidden_value):
|
||
... for item in iterable:
|
||
... if item == forbidden_value:
|
||
... raise ValueError
|
||
... yield item
|
||
...
|
||
>>> list(iter_index(assert_no_value('AABCADEAF', 'B'), 'A'))
|
||
Traceback (most recent call last):
|
||
...
|
||
ValueError
|
||
>>> # Verify that both paths can find identical NaN values
|
||
>>> x = float('NaN')
|
||
>>> y = float('NaN')
|
||
>>> list(iter_index([0, x, x, y, 0], x))
|
||
[1, 2]
|
||
>>> list(iter_index(iter([0, x, x, y, 0]), x))
|
||
[1, 2]
|
||
>>> # Test list input. Lists do not support None for the stop argument
|
||
>>> list(iter_index(list('AABCADEAF'), 'A'))
|
||
[0, 1, 4, 7]
|
||
>>> # Verify that input is consumed lazily
|
||
>>> input_iterator = iter('AABCADEAF')
|
||
>>> output_iterator = iter_index(input_iterator, 'A')
|
||
>>> next(output_iterator)
|
||
0
|
||
>>> next(output_iterator)
|
||
1
|
||
>>> next(output_iterator)
|
||
4
|
||
>>> ''.join(input_iterator)
|
||
'DEAF'
|
||
|
||
|
||
>>> # Verify that the target value can be a sequence.
|
||
>>> seq = [[10, 20], [30, 40], 30, 40, [30, 40], 50]
|
||
>>> target = [30, 40]
|
||
>>> list(iter_index(seq, target))
|
||
[1, 4]
|
||
|
||
|
||
>>> # Verify faithfulness to type specific index() method behaviors.
|
||
>>> # For example, bytes and str perform continuous-subsequence searches
|
||
>>> # that do not match the general behavior specified
|
||
>>> # in collections.abc.Sequence.index().
|
||
>>> seq = 'abracadabra'
|
||
>>> target = 'ab'
|
||
>>> list(iter_index(seq, target))
|
||
[0, 7]
|
||
|
||
|
||
>>> list(sieve(30))
|
||
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
|
||
>>> small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
|
||
>>> all(list(sieve(n)) == [p for p in small_primes if p < n] for n in range(101))
|
||
True
|
||
>>> len(list(sieve(100)))
|
||
25
|
||
>>> len(list(sieve(1_000)))
|
||
168
|
||
>>> len(list(sieve(10_000)))
|
||
1229
|
||
>>> len(list(sieve(100_000)))
|
||
9592
|
||
>>> len(list(sieve(1_000_000)))
|
||
78498
|
||
>>> carmichael = {561, 1105, 1729, 2465, 2821, 6601, 8911} # https://oeis.org/A002997
|
||
>>> set(sieve(10_000)).isdisjoint(carmichael)
|
||
True
|
||
|
||
|
||
>>> list(factor(99)) # Code example 1
|
||
[3, 3, 11]
|
||
>>> list(factor(1_000_000_000_000_007)) # Code example 2
|
||
[47, 59, 360620266859]
|
||
>>> list(factor(1_000_000_000_000_403)) # Code example 3
|
||
[1000000000000403]
|
||
>>> list(factor(0))
|
||
[]
|
||
>>> list(factor(1))
|
||
[]
|
||
>>> list(factor(2))
|
||
[2]
|
||
>>> list(factor(3))
|
||
[3]
|
||
>>> list(factor(4))
|
||
[2, 2]
|
||
>>> list(factor(5))
|
||
[5]
|
||
>>> list(factor(6))
|
||
[2, 3]
|
||
>>> list(factor(7))
|
||
[7]
|
||
>>> list(factor(8))
|
||
[2, 2, 2]
|
||
>>> list(factor(9))
|
||
[3, 3]
|
||
>>> list(factor(10))
|
||
[2, 5]
|
||
>>> list(factor(128_884_753_939)) # large prime
|
||
[128884753939]
|
||
>>> list(factor(999953 * 999983)) # large semiprime
|
||
[999953, 999983]
|
||
>>> list(factor(6 ** 20)) == [2] * 20 + [3] * 20 # large power
|
||
True
|
||
>>> list(factor(909_909_090_909)) # large multiterm composite
|
||
[3, 3, 7, 13, 13, 751, 113797]
|
||
>>> math.prod([3, 3, 7, 13, 13, 751, 113797])
|
||
909909090909
|
||
>>> all(math.prod(factor(n)) == n for n in range(1, 2_000))
|
||
True
|
||
>>> all(set(factor(n)) <= set(sieve(n+1)) for n in range(2_000))
|
||
True
|
||
>>> all(list(factor(n)) == sorted(factor(n)) for n in range(2_000))
|
||
True
|
||
|
||
|
||
>>> totient(0) # https://www.wolframalpha.com/input?i=totient+0
|
||
0
|
||
>>> first_totients = [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6,
|
||
... 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18,
|
||
... 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36,
|
||
... 28, 58, 16, 60, 30, 36, 32, 48, 20, 66, 32, 44] # https://oeis.org/A000010
|
||
...
|
||
>>> list(map(totient, range(1, 70))) == first_totients
|
||
True
|
||
>>> reference_totient = lambda n: sum(math.gcd(t, n) == 1 for t in range(1, n+1))
|
||
>>> all(totient(n) == reference_totient(n) for n in range(1000))
|
||
True
|
||
>>> totient(128_884_753_939) == 128_884_753_938 # large prime
|
||
True
|
||
>>> totient(999953 * 999983) == 999952 * 999982 # large semiprime
|
||
True
|
||
>>> totient(6 ** 20) == 1 * 2**19 * 2 * 3**19 # repeated primes
|
||
True
|
||
|
||
|
||
>>> list(flatten([('a', 'b'), (), ('c', 'd', 'e'), ('f',), ('g', 'h', 'i')]))
|
||
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
|
||
|
||
|
||
>>> list(repeatfunc(pow, 5, 2, 3))
|
||
[8, 8, 8, 8, 8]
|
||
>>> take(5, map(int, repeatfunc(random.random)))
|
||
[0, 0, 0, 0, 0]
|
||
>>> random.seed(85753098575309)
|
||
>>> list(repeatfunc(random.random, 3))
|
||
[0.16370491282496968, 0.45889608687313455, 0.3747076837820118]
|
||
>>> list(repeatfunc(chr, 3, 65))
|
||
['A', 'A', 'A']
|
||
>>> list(repeatfunc(pow, 3, 2, 5))
|
||
[32, 32, 32]
|
||
|
||
|
||
>>> list(grouper('abcdefg', 3, fillvalue='x'))
|
||
[('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'x', 'x')]
|
||
|
||
|
||
>>> it = grouper('abcdefg', 3, incomplete='strict')
|
||
>>> next(it)
|
||
('a', 'b', 'c')
|
||
>>> next(it)
|
||
('d', 'e', 'f')
|
||
>>> next(it)
|
||
Traceback (most recent call last):
|
||
...
|
||
ValueError: zip() argument 2 is shorter than argument 1
|
||
|
||
>>> list(grouper('abcdefg', n=3, incomplete='ignore'))
|
||
[('a', 'b', 'c'), ('d', 'e', 'f')]
|
||
|
||
|
||
>>> list(sliding_window('ABCDEFG', 1))
|
||
[('A',), ('B',), ('C',), ('D',), ('E',), ('F',), ('G',)]
|
||
>>> list(sliding_window('ABCDEFG', 2))
|
||
[('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E'), ('E', 'F'), ('F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 3))
|
||
[('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 4))
|
||
[('A', 'B', 'C', 'D'), ('B', 'C', 'D', 'E'), ('C', 'D', 'E', 'F'), ('D', 'E', 'F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 5))
|
||
[('A', 'B', 'C', 'D', 'E'), ('B', 'C', 'D', 'E', 'F'), ('C', 'D', 'E', 'F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 6))
|
||
[('A', 'B', 'C', 'D', 'E', 'F'), ('B', 'C', 'D', 'E', 'F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 7))
|
||
[('A', 'B', 'C', 'D', 'E', 'F', 'G')]
|
||
>>> list(sliding_window('ABCDEFG', 8))
|
||
[]
|
||
>>> try:
|
||
... list(sliding_window('ABCDEFG', -1))
|
||
... except ValueError:
|
||
... 'zero or negative n not supported'
|
||
...
|
||
'zero or negative n not supported'
|
||
>>> try:
|
||
... list(sliding_window('ABCDEFG', 0))
|
||
... except ValueError:
|
||
... 'zero or negative n not supported'
|
||
...
|
||
'zero or negative n not supported'
|
||
|
||
|
||
>>> list(roundrobin('abc', 'd', 'ef'))
|
||
['a', 'd', 'e', 'b', 'f', 'c']
|
||
>>> ranges = [range(5, 1000), range(4, 3000), range(0), range(3, 2000), range(2, 5000), range(1, 3500)]
|
||
>>> collections.Counter(roundrobin(*ranges)) == collections.Counter(chain(*ranges))
|
||
True
|
||
>>> # Verify that the inputs are consumed lazily
|
||
>>> input_iterators = list(map(iter, ['abcd', 'ef', '', 'ghijk', 'l', 'mnopqr']))
|
||
>>> output_iterator = roundrobin(*input_iterators)
|
||
>>> ''.join(islice(output_iterator, 10))
|
||
'aeglmbfhnc'
|
||
>>> ''.join(chain(*input_iterators))
|
||
'dijkopqr'
|
||
|
||
|
||
>>> list(subslices('ABCD'))
|
||
['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D']
|
||
|
||
|
||
>>> list(powerset([1,2,3]))
|
||
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
|
||
>>> all(len(list(powerset(range(n)))) == 2**n for n in range(18))
|
||
True
|
||
>>> list(powerset('abcde')) == sorted(sorted(set(powerset('abcde'))), key=len)
|
||
True
|
||
|
||
|
||
>>> list(unique_everseen('AAAABBBCCDAABBB'))
|
||
['A', 'B', 'C', 'D']
|
||
>>> list(unique_everseen('ABBCcAD', str.casefold))
|
||
['A', 'B', 'C', 'D']
|
||
>>> list(unique_everseen('ABBcCAD', str.casefold))
|
||
['A', 'B', 'c', 'D']
|
||
>>> # Verify that the input is consumed lazily
|
||
>>> input_iterator = iter('AAAABBBCCDAABBB')
|
||
>>> output_iterator = unique_everseen(input_iterator)
|
||
>>> next(output_iterator)
|
||
'A'
|
||
>>> ''.join(input_iterator)
|
||
'AAABBBCCDAABBB'
|
||
|
||
|
||
>>> list(unique_justseen('AAAABBBCCDAABBB'))
|
||
['A', 'B', 'C', 'D', 'A', 'B']
|
||
>>> list(unique_justseen('ABBCcAD', str.casefold))
|
||
['A', 'B', 'C', 'A', 'D']
|
||
>>> list(unique_justseen('ABBcCAD', str.casefold))
|
||
['A', 'B', 'c', 'A', 'D']
|
||
>>> # Verify that the input is consumed lazily
|
||
>>> input_iterator = iter('AAAABBBCCDAABBB')
|
||
>>> output_iterator = unique_justseen(input_iterator)
|
||
>>> next(output_iterator)
|
||
'A'
|
||
>>> ''.join(input_iterator)
|
||
'AAABBBCCDAABBB'
|
||
|
||
|
||
>>> list(unique([[1, 2], [3, 4], [1, 2]]))
|
||
[[1, 2], [3, 4]]
|
||
>>> list(unique('ABBcCAD', str.casefold))
|
||
['A', 'B', 'c', 'D']
|
||
>>> list(unique('ABBcCAD', str.casefold, reverse=True))
|
||
['D', 'c', 'B', 'A']
|
||
|
||
|
||
>>> d = dict(a=1, b=2, c=3)
|
||
>>> it = iter_except(d.popitem, KeyError)
|
||
>>> d['d'] = 4
|
||
>>> next(it)
|
||
('d', 4)
|
||
>>> next(it)
|
||
('c', 3)
|
||
>>> next(it)
|
||
('b', 2)
|
||
>>> d['e'] = 5
|
||
>>> next(it)
|
||
('e', 5)
|
||
>>> next(it)
|
||
('a', 1)
|
||
>>> next(it, 'empty')
|
||
'empty'
|
||
|
||
|
||
>>> first_true('ABC0DEF1', '9', str.isdigit)
|
||
'0'
|
||
>>> # Verify that inputs are consumed lazily
|
||
>>> it = iter('ABC0DEF1')
|
||
>>> first_true(it, predicate=str.isdigit)
|
||
'0'
|
||
>>> ''.join(it)
|
||
'DEF1'
|
||
|
||
|
||
.. testcode::
|
||
:hide:
|
||
|
||
# Old recipes and their tests which are guaranteed to continue to work.
|
||
|
||
def sumprod(vec1, vec2):
|
||
"Compute a sum of products."
|
||
return sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))
|
||
|
||
def dotproduct(vec1, vec2):
|
||
return sum(map(operator.mul, vec1, vec2))
|
||
|
||
def pad_none(iterable):
|
||
"""Returns the sequence elements and then returns None indefinitely.
|
||
|
||
Useful for emulating the behavior of the built-in map() function.
|
||
"""
|
||
return chain(iterable, repeat(None))
|
||
|
||
def triplewise(iterable):
|
||
"Return overlapping triplets from an iterable"
|
||
# triplewise('ABCDEFG') → ABC BCD CDE DEF EFG
|
||
for (a, _), (b, c) in pairwise(pairwise(iterable)):
|
||
yield a, b, c
|
||
|
||
def nth_combination(iterable, r, index):
|
||
"Equivalent to list(combinations(iterable, r))[index]"
|
||
pool = tuple(iterable)
|
||
n = len(pool)
|
||
c = math.comb(n, r)
|
||
if index < 0:
|
||
index += c
|
||
if index < 0 or index >= c:
|
||
raise IndexError
|
||
result = []
|
||
while r:
|
||
c, n, r = c*r//n, n-1, r-1
|
||
while index >= c:
|
||
index -= c
|
||
c, n = c*(n-r)//n, n-1
|
||
result.append(pool[-1-n])
|
||
return tuple(result)
|
||
|
||
def before_and_after(predicate, it):
|
||
""" Variant of takewhile() that allows complete
|
||
access to the remainder of the iterator.
|
||
|
||
>>> it = iter('ABCdEfGhI')
|
||
>>> all_upper, remainder = before_and_after(str.isupper, it)
|
||
>>> ''.join(all_upper)
|
||
'ABC'
|
||
>>> ''.join(remainder) # takewhile() would lose the 'd'
|
||
'dEfGhI'
|
||
|
||
Note that the true iterator must be fully consumed
|
||
before the remainder iterator can generate valid results.
|
||
"""
|
||
it = iter(it)
|
||
transition = []
|
||
|
||
def true_iterator():
|
||
for elem in it:
|
||
if predicate(elem):
|
||
yield elem
|
||
else:
|
||
transition.append(elem)
|
||
return
|
||
|
||
return true_iterator(), chain(transition, it)
|
||
|
||
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)
|
||
|
||
|
||
|
||
.. doctest::
|
||
:hide:
|
||
|
||
>>> dotproduct([1,2,3], [4,5,6])
|
||
32
|
||
|
||
|
||
>>> sumprod([1,2,3], [4,5,6])
|
||
32
|
||
|
||
|
||
>>> list(islice(pad_none('abc'), 0, 6))
|
||
['a', 'b', 'c', None, None, None]
|
||
|
||
|
||
>>> list(triplewise('ABCDEFG'))
|
||
[('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')]
|
||
|
||
|
||
>>> population = 'ABCDEFGH'
|
||
>>> for r in range(len(population) + 1):
|
||
... seq = list(combinations(population, r))
|
||
... for i in range(len(seq)):
|
||
... assert nth_combination(population, r, i) == seq[i]
|
||
... for i in range(-len(seq), 0):
|
||
... assert nth_combination(population, r, i) == seq[i]
|
||
...
|
||
>>> iterable = 'abcde'
|
||
>>> r = 3
|
||
>>> combos = list(combinations(iterable, r))
|
||
>>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos))
|
||
True
|
||
|
||
|
||
>>> it = iter('ABCdEfGhI')
|
||
>>> all_upper, remainder = before_and_after(str.isupper, it)
|
||
>>> ''.join(all_upper)
|
||
'ABC'
|
||
>>> ''.join(remainder)
|
||
'dEfGhI'
|
||
|
||
|
||
>>> def is_odd(x):
|
||
... return x % 2 == 1
|
||
...
|
||
>>> evens, odds = partition(is_odd, range(10))
|
||
>>> list(evens)
|
||
[0, 2, 4, 6, 8]
|
||
>>> list(odds)
|
||
[1, 3, 5, 7, 9]
|
||
>>> # Verify that the input is consumed lazily
|
||
>>> input_iterator = iter(range(10))
|
||
>>> evens, odds = partition(is_odd, input_iterator)
|
||
>>> next(odds)
|
||
1
|
||
>>> next(odds)
|
||
3
|
||
>>> next(evens)
|
||
0
|
||
>>> list(input_iterator)
|
||
[4, 5, 6, 7, 8, 9]
|