aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/more-itertools
diff options
context:
space:
mode:
authorivasenkov15 <ivasenkov15@yandex-team.com>2024-01-23 12:12:09 +0300
committerAlexander Smirnov <alex@ydb.tech>2024-01-24 15:02:04 +0300
commitfdca1b46d97eaacd40fb002b285099c64be3752c (patch)
treedeb2fc1ce5b95954b06283783e6d4504bee84bf2 /contrib/python/more-itertools
parent8e1ef6e552adde4b26cda116a183d3889112d238 (diff)
downloadydb-fdca1b46d97eaacd40fb002b285099c64be3752c.tar.gz
fix bank-applications: remove bank-forms usage
Diffstat (limited to 'contrib/python/more-itertools')
-rw-r--r--contrib/python/more-itertools/py3/.dist-info/METADATA10
-rw-r--r--contrib/python/more-itertools/py3/README.rst7
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/__init__.py2
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/more.py143
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/recipes.py107
-rw-r--r--contrib/python/more-itertools/py3/tests/test_more.py287
-rw-r--r--contrib/python/more-itertools/py3/tests/test_recipes.py76
-rw-r--r--contrib/python/more-itertools/py3/ya.make2
8 files changed, 545 insertions, 89 deletions
diff --git a/contrib/python/more-itertools/py3/.dist-info/METADATA b/contrib/python/more-itertools/py3/.dist-info/METADATA
index 6f7dfa1824..f54f1ff279 100644
--- a/contrib/python/more-itertools/py3/.dist-info/METADATA
+++ b/contrib/python/more-itertools/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 10.1.0
+Version: 10.2.0
Summary: More routines for operating on iterables, beyond itertools
Keywords: itertools,iterator,iteration,filter,peek,peekable,chunk,chunked
Author-email: Erik Rose <erikrose@grinchcentral.com>
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -120,6 +121,8 @@ Python iterables.
| | `rstrip <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.rstrip>`_, |
| | `filter_except <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.filter_except>`_, |
| | `map_except <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.map_except>`_, |
+| | `filter_map <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.filter_map>`_, |
+| | `iter_suppress <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iter_suppress>`_, |
| | `nth_or_last <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_or_last>`_, |
| | `unique_in_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_in_window>`_, |
| | `before_and_after <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.before_and_after>`_, |
@@ -130,6 +133,7 @@ Python iterables.
| | `unique_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_justseen>`_, |
| | `duplicates_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_everseen>`_, |
| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_, |
+| | `classify_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.classify_unique>`_, |
| | `longest_common_prefix <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.longest_common_prefix>`_, |
| | `takewhile_inclusive <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.takewhile_inclusive>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -182,7 +186,9 @@ Python iterables.
| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_, |
| | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
| | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_, |
-| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_ |
+| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_, |
+| | `totient <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.totient>`_, |
+| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/contrib/python/more-itertools/py3/README.rst b/contrib/python/more-itertools/py3/README.rst
index 0d9d27d216..0786bf3f3e 100644
--- a/contrib/python/more-itertools/py3/README.rst
+++ b/contrib/python/more-itertools/py3/README.rst
@@ -97,6 +97,8 @@ Python iterables.
| | `rstrip <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.rstrip>`_, |
| | `filter_except <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.filter_except>`_, |
| | `map_except <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.map_except>`_, |
+| | `filter_map <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.filter_map>`_, |
+| | `iter_suppress <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iter_suppress>`_, |
| | `nth_or_last <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_or_last>`_, |
| | `unique_in_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_in_window>`_, |
| | `before_and_after <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.before_and_after>`_, |
@@ -107,6 +109,7 @@ Python iterables.
| | `unique_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_justseen>`_, |
| | `duplicates_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_everseen>`_, |
| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_, |
+| | `classify_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.classify_unique>`_, |
| | `longest_common_prefix <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.longest_common_prefix>`_, |
| | `takewhile_inclusive <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.takewhile_inclusive>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -159,7 +162,9 @@ Python iterables.
| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_, |
| | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
| | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_, |
-| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_ |
+| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_, |
+| | `totient <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.totient>`_, |
+| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/contrib/python/more-itertools/py3/more_itertools/__init__.py b/contrib/python/more-itertools/py3/more_itertools/__init__.py
index 28ffadcf8d..aff94a9abd 100644
--- a/contrib/python/more-itertools/py3/more_itertools/__init__.py
+++ b/contrib/python/more-itertools/py3/more_itertools/__init__.py
@@ -3,4 +3,4 @@
from .more import * # noqa
from .recipes import * # noqa
-__version__ = '10.1.0'
+__version__ = '10.2.0'
diff --git a/contrib/python/more-itertools/py3/more_itertools/more.py b/contrib/python/more-itertools/py3/more_itertools/more.py
index 59c2f1a499..dd711a4763 100644
--- a/contrib/python/more-itertools/py3/more_itertools/more.py
+++ b/contrib/python/more-itertools/py3/more_itertools/more.py
@@ -19,7 +19,7 @@ from itertools import (
zip_longest,
product,
)
-from math import exp, factorial, floor, log
+from math import exp, factorial, floor, log, perm, comb
from queue import Empty, Queue
from random import random, randrange, uniform
from operator import itemgetter, mul, sub, gt, lt, ge, le
@@ -68,8 +68,10 @@ __all__ = [
'divide',
'duplicates_everseen',
'duplicates_justseen',
+ 'classify_unique',
'exactly_n',
'filter_except',
+ 'filter_map',
'first',
'gray_product',
'groupby_transform',
@@ -83,6 +85,7 @@ __all__ = [
'is_sorted',
'islice_extended',
'iterate',
+ 'iter_suppress',
'last',
'locate',
'longest_common_prefix',
@@ -198,15 +201,14 @@ def first(iterable, default=_marker):
``next(iter(iterable), default)``.
"""
- try:
- return next(iter(iterable))
- except StopIteration as e:
- if default is _marker:
- raise ValueError(
- 'first() was called on an empty iterable, and no '
- 'default value was provided.'
- ) from e
- return default
+ for item in iterable:
+ return item
+ if default is _marker:
+ raise ValueError(
+ 'first() was called on an empty iterable, and no '
+ 'default value was provided.'
+ )
+ return default
def last(iterable, default=_marker):
@@ -582,6 +584,9 @@ def strictly_n(iterable, n, too_short=None, too_long=None):
>>> list(strictly_n(iterable, n))
['a', 'b', 'c', 'd']
+ Note that the returned iterable must be consumed in order for the check to
+ be made.
+
By default, *too_short* and *too_long* are functions that raise
``ValueError``.
@@ -919,7 +924,7 @@ def substrings_indexes(seq, reverse=False):
class bucket:
- """Wrap *iterable* and return an object that buckets it iterable into
+ """Wrap *iterable* and return an object that buckets the iterable into
child iterables based on a *key* function.
>>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3']
@@ -3222,6 +3227,8 @@ class time_limited:
stops if the time elapsed is greater than *limit_seconds*. If your time
limit is 1 second, but it takes 2 seconds to generate the first item from
the iterable, the function will run for 2 seconds and not yield anything.
+ As a special case, when *limit_seconds* is zero, the iterator never
+ returns anything.
"""
@@ -3237,6 +3244,9 @@ class time_limited:
return self
def __next__(self):
+ if self.limit_seconds == 0:
+ self.timed_out = True
+ raise StopIteration
item = next(self._iterable)
if monotonic() - self._start_time > self.limit_seconds:
self.timed_out = True
@@ -3356,7 +3366,7 @@ def iequals(*iterables):
>>> iequals("abc", "acb")
False
- Not to be confused with :func:`all_equals`, which checks whether all
+ Not to be confused with :func:`all_equal`, which checks whether all
elements of iterable are equal to each other.
"""
@@ -3853,7 +3863,7 @@ def nth_permutation(iterable, r, index):
elif not 0 <= r < n:
raise ValueError
else:
- c = factorial(n) // factorial(n - r)
+ c = perm(n, r)
if index < 0:
index += c
@@ -3898,7 +3908,7 @@ def nth_combination_with_replacement(iterable, r, index):
if (r < 0) or (r > n):
raise ValueError
- c = factorial(n + r - 1) // (factorial(r) * factorial(n - 1))
+ c = comb(n + r - 1, r)
if index < 0:
index += c
@@ -3911,9 +3921,7 @@ def nth_combination_with_replacement(iterable, r, index):
while r:
r -= 1
while n >= 0:
- num_combs = factorial(n + r - 1) // (
- factorial(r) * factorial(n - 1)
- )
+ num_combs = comb(n + r - 1, r)
if index < num_combs:
break
n -= 1
@@ -4015,9 +4023,9 @@ def combination_index(element, iterable):
for i, j in enumerate(reversed(indexes), start=1):
j = n - j
if i <= j:
- index += factorial(j) // (factorial(i) * factorial(j - i))
+ index += comb(j, i)
- return factorial(n + 1) // (factorial(k + 1) * factorial(n - k)) - index
+ return comb(n + 1, k + 1) - index
def combination_with_replacement_index(element, iterable):
@@ -4057,7 +4065,7 @@ def combination_with_replacement_index(element, iterable):
break
else:
raise ValueError(
- 'element is not a combination with replacment of iterable'
+ 'element is not a combination with replacement of iterable'
)
n = len(pool)
@@ -4066,11 +4074,13 @@ def combination_with_replacement_index(element, iterable):
occupations[p] += 1
index = 0
+ cumulative_sum = 0
for k in range(1, n):
- j = l + n - 1 - k - sum(occupations[:k])
+ cumulative_sum += occupations[k - 1]
+ j = l + n - 1 - k - cumulative_sum
i = n - k
if i <= j:
- index += factorial(j) // (factorial(i) * factorial(j - i))
+ index += comb(j, i)
return index
@@ -4296,7 +4306,7 @@ def duplicates_everseen(iterable, key=None):
>>> list(duplicates_everseen('AaaBbbCccAaa', str.lower))
['a', 'a', 'b', 'b', 'c', 'c', 'A', 'a', 'a']
- This function is analagous to :func:`unique_everseen` and is subject to
+ This function is analogous to :func:`unique_everseen` and is subject to
the same performance considerations.
"""
@@ -4326,12 +4336,54 @@ def duplicates_justseen(iterable, key=None):
>>> list(duplicates_justseen('AaaBbbCccAaa', str.lower))
['a', 'a', 'b', 'b', 'c', 'c', 'a', 'a']
- This function is analagous to :func:`unique_justseen`.
+ This function is analogous to :func:`unique_justseen`.
"""
return flatten(g for _, g in groupby(iterable, key) for _ in g)
+def classify_unique(iterable, key=None):
+ """Classify each element in terms of its uniqueness.
+
+ For each element in the input iterable, return a 3-tuple consisting of:
+
+ 1. The element itself
+ 2. ``False`` if the element is equal to the one preceding it in the input,
+ ``True`` otherwise (i.e. the equivalent of :func:`unique_justseen`)
+ 3. ``False`` if this element has been seen anywhere in the input before,
+ ``True`` otherwise (i.e. the equivalent of :func:`unique_everseen`)
+
+ >>> list(classify_unique('otto')) # doctest: +NORMALIZE_WHITESPACE
+ [('o', True, True),
+ ('t', True, True),
+ ('t', False, False),
+ ('o', True, False)]
+
+ This function is analogous to :func:`unique_everseen` and is subject to
+ the same performance considerations.
+
+ """
+ seen_set = set()
+ seen_list = []
+ use_key = key is not None
+ previous = None
+
+ for i, element in enumerate(iterable):
+ k = key(element) if use_key else element
+ is_unique_justseen = not i or previous != k
+ previous = k
+ is_unique_everseen = False
+ try:
+ if k not in seen_set:
+ seen_set.add(k)
+ is_unique_everseen = True
+ except TypeError:
+ if k not in seen_list:
+ seen_list.append(k)
+ is_unique_everseen = True
+ yield element, is_unique_justseen, is_unique_everseen
+
+
def minmax(iterable_or_value, *others, key=None, default=_marker):
"""Returns both the smallest and largest items in an iterable
or the largest of two or more arguments.
@@ -4529,10 +4581,8 @@ def takewhile_inclusive(predicate, iterable):
:func:`takewhile` would return ``[1, 4]``.
"""
for x in iterable:
- if predicate(x):
- yield x
- else:
- yield x
+ yield x
+ if not predicate(x):
break
@@ -4567,3 +4617,40 @@ def outer_product(func, xs, ys, *args, **kwargs):
starmap(lambda x, y: func(x, y, *args, **kwargs), product(xs, ys)),
n=len(ys),
)
+
+
+def iter_suppress(iterable, *exceptions):
+ """Yield each of the items from *iterable*. If the iteration raises one of
+ the specified *exceptions*, that exception will be suppressed and iteration
+ will stop.
+
+ >>> from itertools import chain
+ >>> def breaks_at_five(x):
+ ... while True:
+ ... if x >= 5:
+ ... raise RuntimeError
+ ... yield x
+ ... x += 1
+ >>> it_1 = iter_suppress(breaks_at_five(1), RuntimeError)
+ >>> it_2 = iter_suppress(breaks_at_five(2), RuntimeError)
+ >>> list(chain(it_1, it_2))
+ [1, 2, 3, 4, 2, 3, 4]
+ """
+ try:
+ yield from iterable
+ except exceptions:
+ return
+
+
+def filter_map(func, iterable):
+ """Apply *func* to every element of *iterable*, yielding only those which
+ are not ``None``.
+
+ >>> elems = ['1', 'a', '2', 'b', '3']
+ >>> list(filter_map(lambda s: int(s) if s.isnumeric() else None, elems))
+ [1, 2, 3]
+ """
+ for x in iterable:
+ y = func(x)
+ if y is not None:
+ yield y
diff --git a/contrib/python/more-itertools/py3/more_itertools/recipes.py b/contrib/python/more-itertools/py3/more_itertools/recipes.py
index a0bdbecec6..145e3cb5bd 100644
--- a/contrib/python/more-itertools/py3/more_itertools/recipes.py
+++ b/contrib/python/more-itertools/py3/more_itertools/recipes.py
@@ -28,6 +28,7 @@ from itertools import (
zip_longest,
)
from random import randrange, sample, choice
+from sys import hexversion
__all__ = [
'all_equal',
@@ -56,6 +57,7 @@ __all__ = [
'powerset',
'prepend',
'quantify',
+ 'reshape',
'random_combination_with_replacement',
'random_combination',
'random_permutation',
@@ -69,6 +71,7 @@ __all__ = [
'tabulate',
'tail',
'take',
+ 'totient',
'transpose',
'triplewise',
'unique_everseen',
@@ -492,7 +495,7 @@ def unique_everseen(iterable, key=None):
>>> list(unique_everseen(iterable, key=tuple)) # Faster
[[1, 2], [2, 3]]
- Similary, you may want to convert unhashable ``set`` objects with
+ Similarly, you may want to convert unhashable ``set`` objects with
``key=frozenset``. For ``dict`` objects,
``key=lambda x: frozenset(x.items())`` can be used.
@@ -524,6 +527,9 @@ def unique_justseen(iterable, key=None):
['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)))
@@ -817,35 +823,34 @@ def polynomial_from_roots(roots):
return list(reduce(convolve, factors, [1]))
-def iter_index(iterable, value, start=0):
+def iter_index(iterable, value, start=0, stop=None):
"""Yield the index of each place in *iterable* that *value* occurs,
- beginning with index *start*.
+ beginning with index *start* and ending before index *stop*.
See :func:`locate` for a more general means of finding the indexes
associated with particular values.
>>> list(iter_index('AABCADEAF', 'A'))
[0, 1, 4, 7]
+ >>> list(iter_index('AABCADEAF', 'A', 1)) # start index is inclusive
+ [1, 4, 7]
+ >>> list(iter_index('AABCADEAF', 'A', 1, 7)) # stop index is not inclusive
+ [1, 4]
"""
- try:
- seq_index = iterable.index
- except AttributeError:
+ seq_index = getattr(iterable, 'index', None)
+ if seq_index is None:
# Slow path for general iterables
- it = islice(iterable, start, None)
- i = start - 1
- try:
- while True:
- i = i + operator.indexOf(it, value) + 1
+ it = islice(iterable, start, stop)
+ for i, element in enumerate(it, start):
+ if element is value or element == value:
yield i
- except ValueError:
- pass
else:
# Fast path for sequences
+ stop = len(iterable) if stop is None else stop
i = start - 1
try:
while True:
- i = seq_index(value, i + 1)
- yield i
+ yield (i := seq_index(value, i + 1, stop))
except ValueError:
pass
@@ -856,47 +861,52 @@ def sieve(n):
>>> list(sieve(30))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
"""
+ if n > 2:
+ yield 2
+ start = 3
data = bytearray((0, 1)) * (n // 2)
- data[:3] = 0, 0, 0
limit = math.isqrt(n) + 1
- for p in compress(range(limit), data):
+ for p in iter_index(data, 1, start, limit):
+ yield from iter_index(data, 1, start, p * p)
data[p * p : n : p + p] = bytes(len(range(p * p, n, p + p)))
- data[2] = 1
- return iter_index(data, 1) if n > 2 else iter([])
+ start = p * p
+ yield from iter_index(data, 1, start)
-def _batched(iterable, n):
- """Batch data into lists of length *n*. The last batch may be shorter.
+def _batched(iterable, n, *, strict=False):
+ """Batch data into tuples of length *n*. If the number of items in
+ *iterable* is not divisible by *n*:
+ * The last batch will be shorter if *strict* is ``False``.
+ * :exc:`ValueError` will be raised if *strict* is ``True``.
>>> list(batched('ABCDEFG', 3))
[('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]
- On Python 3.12 and above, this is an alias for :func:`itertools.batched`.
+ On Python 3.13 and above, this is an alias for :func:`itertools.batched`.
"""
if n < 1:
raise ValueError('n must be at least one')
it = iter(iterable)
- while True:
- batch = tuple(islice(it, n))
- if not batch:
- break
+ while batch := tuple(islice(it, n)):
+ if strict and len(batch) != n:
+ raise ValueError('batched(): incomplete batch')
yield batch
-try:
+if hexversion >= 0x30D00A2:
from itertools import batched as itertools_batched
-except ImportError:
- batched = _batched
-else:
- def batched(iterable, n):
- return itertools_batched(iterable, n)
+ def batched(iterable, n, *, strict=False):
+ return itertools_batched(iterable, n, strict=strict)
+
+else:
+ batched = _batched
batched.__doc__ = _batched.__doc__
def transpose(it):
- """Swap the rows and columns of the input.
+ """Swap the rows and columns of the input matrix.
>>> list(transpose([(1, 2, 3), (11, 22, 33)]))
[(1, 11), (2, 22), (3, 33)]
@@ -907,8 +917,20 @@ def transpose(it):
return _zip_strict(*it)
+def reshape(matrix, cols):
+ """Reshape the 2-D input *matrix* to have a column count given by *cols*.
+
+ >>> matrix = [(0, 1), (2, 3), (4, 5)]
+ >>> cols = 3
+ >>> list(reshape(matrix, cols))
+ [(0, 1, 2), (3, 4, 5)]
+ """
+ return batched(chain.from_iterable(matrix), cols)
+
+
def matmul(m1, m2):
"""Multiply two matrices.
+
>>> list(matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]))
[(49, 80), (41, 60)]
@@ -921,13 +943,12 @@ def matmul(m1, m2):
def factor(n):
"""Yield the prime factors of n.
+
>>> list(factor(360))
[2, 2, 2, 3, 3, 5]
"""
for prime in sieve(math.isqrt(n) + 1):
- while True:
- if n % prime:
- break
+ while not n % prime:
yield prime
n //= prime
if n == 1:
@@ -975,3 +996,17 @@ def polynomial_derivative(coefficients):
n = len(coefficients)
powers = reversed(range(1, n))
return list(map(operator.mul, coefficients, powers))
+
+
+def totient(n):
+ """Return the count of natural numbers up to *n* that are coprime with *n*.
+
+ >>> totient(9)
+ 6
+ >>> totient(12)
+ 4
+ """
+ for p in unique_justseen(factor(n)):
+ n = n // p * (p - 1)
+
+ return n
diff --git a/contrib/python/more-itertools/py3/tests/test_more.py b/contrib/python/more-itertools/py3/tests/test_more.py
index 77547eb812..741ef1a468 100644
--- a/contrib/python/more-itertools/py3/tests/test_more.py
+++ b/contrib/python/more-itertools/py3/tests/test_more.py
@@ -112,17 +112,9 @@ class FirstTests(TestCase):
def test_one(self):
self.assertEqual(mi.first([3]), 3)
- def test_empty_stop_iteration(self):
- try:
+ def test_empty(self):
+ with self.assertRaises(ValueError):
mi.first([])
- except ValueError:
- formatted_exc = format_exc()
- self.assertIn('StopIteration', formatted_exc)
- self.assertIn(
- 'The above exception was the direct cause', formatted_exc
- )
- else:
- self.fail()
def test_default(self):
self.assertEqual(mi.first([], 'boo'), 'boo')
@@ -160,10 +152,8 @@ class LastTests(TestCase):
(iter(range(1)), 0),
(IterOnlyRange(5), 4),
({n: str(n) for n in range(5)}, 4),
+ ({0: '0', -1: '-1', 2: '-2'}, 2),
]
- # Versions below 3.6.0 don't have ordered dicts
- if version_info >= (3, 6, 0):
- cases.append(({0: '0', -1: '-1', 2: '-2'}, 2))
for iterable, expected in cases:
with self.subTest(iterable=iterable):
@@ -255,7 +245,7 @@ class PeekableTests(PeekableMixinTests, TestCase):
self.assertEqual(p[0], 'a')
self.assertEqual(next(p), 'a')
- # Indexing further into the peekable shouldn't advance the itertor
+ # Indexing further into the peekable shouldn't advance the iterator
self.assertEqual(p[2], 'd')
self.assertEqual(next(p), 'b')
@@ -327,7 +317,7 @@ class PeekableTests(PeekableMixinTests, TestCase):
# prepend() behavior tests
def test_prepend(self):
- """Tests intersperesed ``prepend()`` and ``next()`` calls"""
+ """Tests interspersed ``prepend()`` and ``next()`` calls"""
it = mi.peekable(range(2))
actual = []
@@ -3259,7 +3249,6 @@ class DifferenceTest(TestCase):
def test_empty(self):
self.assertEqual(list(mi.difference([])), [])
- @skipIf(version_info[:2] < (3, 8), 'accumulate with initial needs 3.8+')
def test_initial(self):
original = list(range(100))
accumulated = accumulate(original, initial=100)
@@ -4140,7 +4129,7 @@ class SampleTests(TestCase):
expected = min(k, len(data))
self.assertEqual(actual, expected)
- def test_samling_entire_iterable(self):
+ def test_sampling_entire_iterable(self):
"""If k=len(iterable), the sample contains the original elements."""
data = ["a", 2, "a", 4, (1, 2, 3)]
actual = set(mi.sample(data, k=len(data)))
@@ -4148,7 +4137,7 @@ class SampleTests(TestCase):
self.assertEqual(actual, expected)
def test_scale_invariance_of_weights(self):
- """The probabilit of chosing element a_i is w_i / sum(weights).
+ """The probability of choosing element a_i is w_i / sum(weights).
Scaling weights should not change the probability or outcome."""
data = "abcdef"
@@ -5130,6 +5119,197 @@ class DuplicatesJustSeenTests(TestCase):
self.assertEqual(list(mi.duplicates_justseen(iterable)), [[5, 6]])
+class ClassifyUniqueTests(TestCase):
+ def test_basic(self):
+ self.assertEqual(
+ list(mi.classify_unique('mississippi')),
+ [
+ ('m', True, True),
+ ('i', True, True),
+ ('s', True, True),
+ ('s', False, False),
+ ('i', True, False),
+ ('s', True, False),
+ ('s', False, False),
+ ('i', True, False),
+ ('p', True, True),
+ ('p', False, False),
+ ('i', True, False),
+ ],
+ )
+
+ def test_non_hashable(self):
+ self.assertEqual(
+ list(mi.classify_unique([[1, 2], [3, 4], [3, 4], [1, 2]])),
+ [
+ ([1, 2], True, True),
+ ([3, 4], True, True),
+ ([3, 4], False, False),
+ ([1, 2], True, False),
+ ],
+ )
+
+ def test_partially_hashable(self):
+ self.assertEqual(
+ list(
+ mi.classify_unique(
+ [[1, 2], [3, 4], (5, 6), (5, 6), (3, 4), [1, 2]]
+ )
+ ),
+ [
+ ([1, 2], True, True),
+ ([3, 4], True, True),
+ ((5, 6), True, True),
+ ((5, 6), False, False),
+ ((3, 4), True, True),
+ ([1, 2], True, False),
+ ],
+ )
+
+ def test_key_hashable(self):
+ iterable = 'HEheHHHhEheeEe'
+ self.assertEqual(
+ list(mi.classify_unique(iterable)),
+ [
+ ('H', True, True),
+ ('E', True, True),
+ ('h', True, True),
+ ('e', True, True),
+ ('H', True, False),
+ ('H', False, False),
+ ('H', False, False),
+ ('h', True, False),
+ ('E', True, False),
+ ('h', True, False),
+ ('e', True, False),
+ ('e', False, False),
+ ('E', True, False),
+ ('e', True, False),
+ ],
+ )
+ self.assertEqual(
+ list(mi.classify_unique(iterable, str.lower)),
+ [
+ ('H', True, True),
+ ('E', True, True),
+ ('h', True, False),
+ ('e', True, False),
+ ('H', True, False),
+ ('H', False, False),
+ ('H', False, False),
+ ('h', False, False),
+ ('E', True, False),
+ ('h', True, False),
+ ('e', True, False),
+ ('e', False, False),
+ ('E', False, False),
+ ('e', False, False),
+ ],
+ )
+
+ def test_key_non_hashable(self):
+ iterable = [[1, 2], [3, 0], [5, -2], [5, 6], [1, 2]]
+ self.assertEqual(
+ list(mi.classify_unique(iterable, lambda x: x)),
+ [
+ ([1, 2], True, True),
+ ([3, 0], True, True),
+ ([5, -2], True, True),
+ ([5, 6], True, True),
+ ([1, 2], True, False),
+ ],
+ )
+ self.assertEqual(
+ list(mi.classify_unique(iterable, sum)),
+ [
+ ([1, 2], True, True),
+ ([3, 0], False, False),
+ ([5, -2], False, False),
+ ([5, 6], True, True),
+ ([1, 2], True, False),
+ ],
+ )
+
+ def test_key_partially_hashable(self):
+ iterable = [[1, 2], (1, 2), [1, 2], [5, 6], [1, 2]]
+ self.assertEqual(
+ list(mi.classify_unique(iterable, lambda x: x)),
+ [
+ ([1, 2], True, True),
+ ((1, 2), True, True),
+ ([1, 2], True, False),
+ ([5, 6], True, True),
+ ([1, 2], True, False),
+ ],
+ )
+ self.assertEqual(
+ list(mi.classify_unique(iterable, list)),
+ [
+ ([1, 2], True, True),
+ ((1, 2), False, False),
+ ([1, 2], False, False),
+ ([5, 6], True, True),
+ ([1, 2], True, False),
+ ],
+ )
+
+ def test_vs_unique_everseen(self):
+ input = 'AAAABBBBCCDAABBB'
+ output = [e for e, j, u in mi.classify_unique(input) if u]
+ self.assertEqual(output, ['A', 'B', 'C', 'D'])
+ self.assertEqual(list(mi.unique_everseen(input)), output)
+
+ def test_vs_unique_everseen_key(self):
+ input = 'aAbACCc'
+ output = [e for e, j, u in mi.classify_unique(input, str.lower) if u]
+ self.assertEqual(output, list('abC'))
+ self.assertEqual(list(mi.unique_everseen(input, str.lower)), output)
+
+ def test_vs_unique_justseen(self):
+ input = 'AAAABBBCCDABB'
+ output = [e for e, j, u in mi.classify_unique(input) if j]
+ self.assertEqual(output, list('ABCDAB'))
+ self.assertEqual(list(mi.unique_justseen(input)), output)
+
+ def test_vs_unique_justseen_key(self):
+ input = 'AABCcAD'
+ output = [e for e, j, u in mi.classify_unique(input, str.lower) if j]
+ self.assertEqual(output, list('ABCAD'))
+ self.assertEqual(list(mi.unique_justseen(input, str.lower)), output)
+
+ def test_vs_duplicates_everseen(self):
+ input = [1, 2, 1, 2]
+ output = [e for e, j, u in mi.classify_unique(input) if not u]
+ self.assertEqual(output, [1, 2])
+ self.assertEqual(list(mi.duplicates_everseen(input)), output)
+
+ def test_vs_duplicates_everseen_key(self):
+ input = 'HEheHEhe'
+ output = [
+ e for e, j, u in mi.classify_unique(input, str.lower) if not u
+ ]
+ self.assertEqual(output, list('heHEhe'))
+ self.assertEqual(
+ list(mi.duplicates_everseen(input, str.lower)), output
+ )
+
+ def test_vs_duplicates_justseen(self):
+ input = [1, 2, 3, 3, 2, 2]
+ output = [e for e, j, u in mi.classify_unique(input) if not j]
+ self.assertEqual(output, [3, 2])
+ self.assertEqual(list(mi.duplicates_justseen(input)), output)
+
+ def test_vs_duplicates_justseen_key(self):
+ input = 'HEheHHHhEheeEe'
+ output = [
+ e for e, j, u in mi.classify_unique(input, str.lower) if not j
+ ]
+ self.assertEqual(output, list('HHheEe'))
+ self.assertEqual(
+ list(mi.duplicates_justseen(input, str.lower)), output
+ )
+
+
class LongestCommonPrefixTests(TestCase):
def test_basic(self):
iterables = [[1, 2], [1, 2, 3], [1, 2, 4]]
@@ -5482,3 +5662,74 @@ class OuterProductTests(TestCase):
('Goodbye, Alice!', 'Goodbye, Bob!', 'Goodbye, Carol!'),
]
self.assertEqual(result, expected)
+
+
+class IterSuppressTests(TestCase):
+ class Producer:
+ def __init__(self, exc, die_early=False):
+ self.exc = exc
+ self.pos = 0
+ self.die_early = die_early
+
+ def __iter__(self):
+ if self.die_early:
+ raise self.exc
+
+ return self
+
+ def __next__(self):
+ ret = self.pos
+ if self.pos >= 5:
+ raise self.exc
+ self.pos += 1
+ return ret
+
+ def test_no_error(self):
+ iterator = range(5)
+ actual = list(mi.iter_suppress(iterator, RuntimeError))
+ expected = [0, 1, 2, 3, 4]
+ self.assertEqual(actual, expected)
+
+ def test_raises_error(self):
+ iterator = self.Producer(ValueError)
+ with self.assertRaises(ValueError):
+ list(mi.iter_suppress(iterator, RuntimeError))
+
+ def test_suppression(self):
+ iterator = self.Producer(ValueError)
+ actual = list(mi.iter_suppress(iterator, RuntimeError, ValueError))
+ expected = [0, 1, 2, 3, 4]
+ self.assertEqual(actual, expected)
+
+ def test_early_suppression(self):
+ iterator = self.Producer(ValueError, die_early=True)
+ actual = list(mi.iter_suppress(iterator, RuntimeError, ValueError))
+ expected = []
+ self.assertEqual(actual, expected)
+
+
+class FilterMapTests(TestCase):
+ def test_no_iterables(self):
+ actual = list(mi.filter_map(lambda _: None, []))
+ expected = []
+ self.assertEqual(actual, expected)
+
+ def test_filter(self):
+ actual = list(mi.filter_map(lambda _: None, [1, 2, 3]))
+ expected = []
+ self.assertEqual(actual, expected)
+
+ def test_map(self):
+ actual = list(mi.filter_map(lambda x: x + 1, [1, 2, 3]))
+ expected = [2, 3, 4]
+ self.assertEqual(actual, expected)
+
+ def test_filter_map(self):
+ actual = list(
+ mi.filter_map(
+ lambda x: int(x) if x.isnumeric() else None,
+ ['1', 'a', '2', 'b', '3'],
+ )
+ )
+ expected = [1, 2, 3]
+ self.assertEqual(actual, expected)
diff --git a/contrib/python/more-itertools/py3/tests/test_recipes.py b/contrib/python/more-itertools/py3/tests/test_recipes.py
index 4228913d13..ee5a5233b5 100644
--- a/contrib/python/more-itertools/py3/tests/test_recipes.py
+++ b/contrib/python/more-itertools/py3/tests/test_recipes.py
@@ -103,7 +103,7 @@ class ConsumeTests(TestCase):
self.assertEqual(0, next(r))
def test_negative_consume(self):
- """Check that negative consumsion throws an error"""
+ """Check that negative consumption throws an error"""
r = (x for x in range(10))
self.assertRaises(ValueError, lambda: mi.consume(r, -1))
@@ -203,7 +203,7 @@ class NcyclesTests(TestCase):
n = mi.ncycles(range(100), 0)
self.assertRaises(StopIteration, lambda: next(n))
- def test_pathalogical_case(self):
+ def test_pathological_case(self):
"""asking for negative cycles should return an empty iterator"""
n = mi.ncycles(range(100), -10)
self.assertRaises(StopIteration, lambda: next(n))
@@ -931,6 +931,11 @@ class IterIndexTests(TestCase):
expected = [0, 1, 4, 7]
self.assertEqual(actual, expected)
+ def test_stop(self):
+ actual = list(mi.iter_index('AABCADEAF', 'A', stop=7))
+ expected = [0, 1, 4]
+ self.assertEqual(actual, expected)
+
class SieveTests(TestCase):
def test_basic(self):
@@ -994,6 +999,15 @@ class BatchedTests(TestCase):
actual = list(mi.batched(iterable, n))
self.assertEqual(actual, expected)
+ def test_strict(self):
+ with self.assertRaises(ValueError):
+ list(mi.batched('ABCDEFG', 3, strict=True))
+
+ self.assertEqual(
+ list(mi.batched('ABCDEF', 3, strict=True)),
+ [('A', 'B', 'C'), ('D', 'E', 'F')],
+ )
+
class TransposeTests(TestCase):
def test_empty(self):
@@ -1022,6 +1036,47 @@ class TransposeTests(TestCase):
self.assertEqual(actual, expected)
+class ReshapeTests(TestCase):
+ def test_empty(self):
+ actual = list(mi.reshape([], 3))
+ self.assertEqual(actual, [])
+
+ def test_zero(self):
+ matrix = [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]
+ with self.assertRaises(ValueError):
+ list(mi.reshape(matrix, 0))
+
+ def test_basic(self):
+ matrix = [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]
+ for cols, expected in (
+ (
+ 1,
+ [
+ (0,),
+ (1,),
+ (2,),
+ (3,),
+ (4,),
+ (5,),
+ (6,),
+ (7,),
+ (8,),
+ (9,),
+ (10,),
+ (11,),
+ ],
+ ),
+ (2, [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11)]),
+ (3, [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]),
+ (4, [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]),
+ (6, [(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)]),
+ (12, [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)]),
+ ):
+ with self.subTest(cols=cols):
+ actual = list(mi.reshape(matrix, cols))
+ self.assertEqual(actual, expected)
+
+
class MatMulTests(TestCase):
def test_n_by_n(self):
actual = list(mi.matmul([(7, 5), (3, 5)], [[2, 5], [7, 9]]))
@@ -1096,3 +1151,20 @@ class PolynomialDerivativeTests(TestCase):
with self.subTest(coefficients=coefficients):
actual = mi.polynomial_derivative(coefficients)
self.assertEqual(actual, expected)
+
+
+class TotientTests(TestCase):
+ def test_basic(self):
+ for n, expected in (
+ (1, 1),
+ (2, 1),
+ (3, 2),
+ (4, 2),
+ (9, 6),
+ (12, 4),
+ (128_884_753_939, 128_884_753_938),
+ (999953 * 999983, 999952 * 999982),
+ (6**20, 1 * 2**19 * 2 * 3**19),
+ ):
+ with self.subTest(n=n):
+ self.assertEqual(mi.totient(n), expected)
diff --git a/contrib/python/more-itertools/py3/ya.make b/contrib/python/more-itertools/py3/ya.make
index a1f49d6dab..a9228cc50e 100644
--- a/contrib/python/more-itertools/py3/ya.make
+++ b/contrib/python/more-itertools/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(10.1.0)
+VERSION(10.2.0)
LICENSE(MIT)