diff options
author | ivasenkov15 <ivasenkov15@yandex-team.com> | 2024-01-23 12:12:09 +0300 |
---|---|---|
committer | Alexander Smirnov <alex@ydb.tech> | 2024-01-24 15:02:04 +0300 |
commit | fdca1b46d97eaacd40fb002b285099c64be3752c (patch) | |
tree | deb2fc1ce5b95954b06283783e6d4504bee84bf2 /contrib/python/more-itertools | |
parent | 8e1ef6e552adde4b26cda116a183d3889112d238 (diff) | |
download | ydb-fdca1b46d97eaacd40fb002b285099c64be3752c.tar.gz |
fix bank-applications: remove bank-forms usage
Diffstat (limited to 'contrib/python/more-itertools')
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) |