summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-09-17 23:38:38 +0300
committerrobot-piglet <[email protected]>2025-09-17 23:51:08 +0300
commit0aadd2b8b6f8a556cd5e17ed31adfaae1b4b456c (patch)
treec14edb738750e4eb120c35fc2eace6a8afce3937 /contrib/python
parent28b8afacb9cf086be40ad51d270a6aa09a3dc2d4 (diff)
Intermediate changes
commit_hash:46fb8895a86e7e705f3f141ab4e54f33d325f394
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/more-itertools/py3/.dist-info/METADATA60
-rw-r--r--contrib/python/more-itertools/py3/README.rst53
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/__init__.py2
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/more.py539
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/more.pyi22
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/recipes.py242
-rw-r--r--contrib/python/more-itertools/py3/more_itertools/recipes.pyi19
-rw-r--r--contrib/python/more-itertools/py3/tests/test_more.py451
-rw-r--r--contrib/python/more-itertools/py3/tests/test_recipes.py134
-rw-r--r--contrib/python/more-itertools/py3/ya.make2
10 files changed, 1293 insertions, 231 deletions
diff --git a/contrib/python/more-itertools/py3/.dist-info/METADATA b/contrib/python/more-itertools/py3/.dist-info/METADATA
index 847e174a706..bb7a3db1099 100644
--- a/contrib/python/more-itertools/py3/.dist-info/METADATA
+++ b/contrib/python/more-itertools/py3/.dist-info/METADATA
@@ -1,15 +1,15 @@
-Metadata-Version: 2.3
+Metadata-Version: 2.4
Name: more-itertools
-Version: 10.7.0
+Version: 10.8.0
Summary: More routines for operating on iterables, beyond itertools
Keywords: itertools,iterator,iteration,filter,peek,peekable,chunk,chunked
Author-email: Erik Rose <[email protected]>
Requires-Python: >=3.9
Description-Content-Type: text/x-rst
+License-Expression: MIT
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
-Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries
+License-File: LICENSE
Project-URL: Documentation, https://more-itertools.readthedocs.io/en/stable/
Project-URL: Homepage, https://github.com/more-itertools/more-itertools
@@ -85,6 +86,7 @@ Python iterables.
| | `interleave <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave>`_, |
| | `interleave_longest <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_longest>`_, |
| | `interleave_evenly <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_evenly>`_, |
+| | `interleave_randomly <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_randomly>`_, |
| | `zip_offset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_offset>`_, |
| | `zip_equal <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_equal>`_, |
| | `zip_broadcast <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_broadcast>`_, |
@@ -105,6 +107,8 @@ Python iterables.
| | `is_sorted <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_sorted>`_, |
| | `all_equal <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_equal>`_, |
| | `all_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_unique>`_, |
+| | `argmin <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.argmin>`_, |
+| | `argmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.argmax>`_, |
| | `minmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.minmax>`_, |
| | `first_true <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.first_true>`_, |
| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_, |
@@ -124,6 +128,7 @@ Python iterables.
| | `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>`_, |
+| | `extract <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.extract>`_, |
| | `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>`_, |
| | `nth <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth>`_, |
@@ -142,39 +147,46 @@ Python iterables.
| | `idft <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.idft>`_, |
| | `convolve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.convolve>`_, |
| | `dotproduct <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.dotproduct>`_, |
-| | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
-| | `is_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_prime>`_, |
| | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_, |
-| | `multinomial <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.multinomial>`_, |
-| | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_, |
| | `polynomial_from_roots <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_from_roots>`_, |
| | `polynomial_derivative <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_derivative>`_, |
| | `polynomial_eval <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_eval>`_, |
-| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_, |
| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_, |
+| | `running_median <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.running_median>`_, |
| | `totient <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.totient>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| Combinatorics | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_, |
-| | `distinct_combinations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_combinations>`_, |
-| | `circular_shifts <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_, |
+| Integer math | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
+| | `is_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_prime>`_, |
+| | `multinomial <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.multinomial>`_, |
+| | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_, |
+| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_ |
++------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| Combinatorics | `circular_shifts <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_, |
+| | `derangements <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.derangements>`_, |
+| | `gray_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.gray_product>`_, |
+| | `outer_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.outer_product>`_, |
| | `partitions <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partitions>`_, |
| | `set_partitions <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions>`_, |
-| | `product_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_, |
+| | `powerset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_, |
+| | `powerset_of_sets <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset_of_sets>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| | `distinct_combinations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_combinations>`_, |
+| | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | `combination_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_index>`_, |
-| | `permutation_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_, |
| | `combination_with_replacement_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_with_replacement_index>`_, |
-| | `gray_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.gray_product>`_, |
-| | `outer_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.outer_product>`_, |
-| | `powerset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_, |
-| | `powerset_of_sets <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset_of_sets>`_, |
-| | `random_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_, |
-| | `random_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_, |
+| | `permutation_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_, |
+| | `product_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| | `nth_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination>`_, |
+| | `nth_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination_with_replacement>`_, |
+| | `nth_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_permutation>`_, |
+| | `nth_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_product>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | `random_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_combination>`_, |
| | `random_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_combination_with_replacement>`_, |
-| | `nth_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_product>`_, |
-| | `nth_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_permutation>`_, |
-| | `nth_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination>`_, |
-| | `nth_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination_with_replacement>`_ |
+| | `random_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_, |
+| | `random_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Wrapping | `always_iterable <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_, |
| | `always_reversible <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_reversible>`_, |
@@ -199,7 +211,7 @@ Python iterables.
| | `consume <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.consume>`_, |
| | `tabulate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.tabulate>`_, |
| | `repeatfunc <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.repeatfunc>`_, |
-| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_ |
+| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_, |
| | `doublestarmap <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.doublestarmap>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/contrib/python/more-itertools/py3/README.rst b/contrib/python/more-itertools/py3/README.rst
index c82a5905e80..17faf606ecd 100644
--- a/contrib/python/more-itertools/py3/README.rst
+++ b/contrib/python/more-itertools/py3/README.rst
@@ -60,6 +60,7 @@ Python iterables.
| | `interleave <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave>`_, |
| | `interleave_longest <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_longest>`_, |
| | `interleave_evenly <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_evenly>`_, |
+| | `interleave_randomly <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.interleave_randomly>`_, |
| | `zip_offset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_offset>`_, |
| | `zip_equal <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_equal>`_, |
| | `zip_broadcast <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_broadcast>`_, |
@@ -80,6 +81,8 @@ Python iterables.
| | `is_sorted <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_sorted>`_, |
| | `all_equal <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_equal>`_, |
| | `all_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_unique>`_, |
+| | `argmin <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.argmin>`_, |
+| | `argmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.argmax>`_, |
| | `minmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.minmax>`_, |
| | `first_true <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.first_true>`_, |
| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_, |
@@ -99,6 +102,7 @@ Python iterables.
| | `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>`_, |
+| | `extract <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.extract>`_, |
| | `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>`_, |
| | `nth <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth>`_, |
@@ -117,39 +121,46 @@ Python iterables.
| | `idft <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.idft>`_, |
| | `convolve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.convolve>`_, |
| | `dotproduct <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.dotproduct>`_, |
-| | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
-| | `is_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_prime>`_, |
| | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_, |
-| | `multinomial <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.multinomial>`_, |
-| | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_, |
| | `polynomial_from_roots <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_from_roots>`_, |
| | `polynomial_derivative <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_derivative>`_, |
| | `polynomial_eval <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_eval>`_, |
-| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_, |
| | `sum_of_squares <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sum_of_squares>`_, |
+| | `running_median <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.running_median>`_, |
| | `totient <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.totient>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| Combinatorics | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_, |
-| | `distinct_combinations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_combinations>`_, |
-| | `circular_shifts <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_, |
+| Integer math | `factor <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.factor>`_, |
+| | `is_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.is_prime>`_, |
+| | `multinomial <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.multinomial>`_, |
+| | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_, |
+| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_ |
++------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| Combinatorics | `circular_shifts <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_, |
+| | `derangements <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.derangements>`_, |
+| | `gray_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.gray_product>`_, |
+| | `outer_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.outer_product>`_, |
| | `partitions <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partitions>`_, |
| | `set_partitions <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions>`_, |
-| | `product_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_, |
+| | `powerset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_, |
+| | `powerset_of_sets <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset_of_sets>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| | `distinct_combinations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_combinations>`_, |
+| | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | `combination_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_index>`_, |
-| | `permutation_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_, |
| | `combination_with_replacement_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_with_replacement_index>`_, |
-| | `gray_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.gray_product>`_, |
-| | `outer_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.outer_product>`_, |
-| | `powerset <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_, |
-| | `powerset_of_sets <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset_of_sets>`_, |
-| | `random_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_, |
-| | `random_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_, |
+| | `permutation_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_, |
+| | `product_index <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| | `nth_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination>`_, |
+| | `nth_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination_with_replacement>`_, |
+| | `nth_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_permutation>`_, |
+| | `nth_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_product>`_ |
+| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | `random_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_combination>`_, |
| | `random_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_combination_with_replacement>`_, |
-| | `nth_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_product>`_, |
-| | `nth_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_permutation>`_, |
-| | `nth_combination <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination>`_, |
-| | `nth_combination_with_replacement <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_combination_with_replacement>`_ |
+| | `random_permutation <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_, |
+| | `random_product <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Wrapping | `always_iterable <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_, |
| | `always_reversible <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_reversible>`_, |
@@ -174,7 +185,7 @@ Python iterables.
| | `consume <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.consume>`_, |
| | `tabulate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.tabulate>`_, |
| | `repeatfunc <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.repeatfunc>`_, |
-| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_ |
+| | `reshape <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.reshape>`_, |
| | `doublestarmap <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.doublestarmap>`_ |
+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/contrib/python/more-itertools/py3/more_itertools/__init__.py b/contrib/python/more-itertools/py3/more_itertools/__init__.py
index 9fec1d275b4..24216c5c1fe 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.7.0'
+__version__ = '10.8.0'
diff --git a/contrib/python/more-itertools/py3/more_itertools/more.py b/contrib/python/more-itertools/py3/more_itertools/more.py
index 0f3d1074e87..bf501956ae6 100644
--- a/contrib/python/more-itertools/py3/more_itertools/more.py
+++ b/contrib/python/more-itertools/py3/more_itertools/more.py
@@ -15,6 +15,7 @@ from itertools import (
dropwhile,
groupby,
islice,
+ permutations,
repeat,
starmap,
takewhile,
@@ -23,9 +24,19 @@ from itertools import (
product,
)
from math import comb, e, exp, factorial, floor, fsum, log, log1p, perm, tau
+from math import ceil
from queue import Empty, Queue
from random import random, randrange, shuffle, uniform
-from operator import itemgetter, mul, sub, gt, lt
+from operator import (
+ attrgetter,
+ is_not,
+ itemgetter,
+ lt,
+ mul,
+ neg,
+ sub,
+ gt,
+)
from sys import hexversion, maxsize
from time import monotonic
@@ -34,7 +45,9 @@ from .recipes import (
_zip_equal,
UnequalIterablesError,
consume,
+ first_true,
flatten,
+ is_prime,
nth,
powerset,
sieve,
@@ -52,6 +65,8 @@ __all__ = [
'all_unique',
'always_iterable',
'always_reversible',
+ 'argmax',
+ 'argmin',
'bucket',
'callback_iter',
'chunked',
@@ -65,6 +80,7 @@ __all__ = [
'consumer',
'count_cycle',
'countable',
+ 'derangements',
'dft',
'difference',
'distinct_combinations',
@@ -76,6 +92,7 @@ __all__ = [
'duplicates_justseen',
'classify_unique',
'exactly_n',
+ 'extract',
'filter_except',
'filter_map',
'first',
@@ -88,6 +105,7 @@ __all__ = [
'interleave',
'interleave_evenly',
'interleave_longest',
+ 'interleave_randomly',
'intersperse',
'is_sorted',
'islice_extended',
@@ -219,7 +237,7 @@ def chunked(iterable, n, strict=False):
raise ValueError('iterable is not divisible by n.')
yield chunk
- return iter(ret())
+ return ret()
else:
return iterator
@@ -267,7 +285,7 @@ def last(iterable, default=_marker):
if isinstance(iterable, Sequence):
return iterable[-1]
# Work around https://bugs.python.org/issue38525
- if hasattr(iterable, '__reversed__'):
+ if getattr(iterable, '__reversed__', None):
return next(reversed(iterable))
return deque(iterable, maxlen=1)[-1]
except (IndexError, TypeError, StopIteration):
@@ -616,8 +634,8 @@ def raise_(exception, *args):
def strictly_n(iterable, n, too_short=None, too_long=None):
"""Validate that *iterable* has exactly *n* items and return them if
it does. If it has fewer than *n* items, call function *too_short*
- with those items. If it has more than *n* items, call function
- *too_long* with the first ``n + 1`` items.
+ with the actual number of items. If it has more than *n* items, call function
+ *too_long* with the number ``n + 1``.
>>> iterable = ['a', 'b', 'c', 'd']
>>> n = 4
@@ -838,6 +856,68 @@ def distinct_permutations(iterable, r=None):
return iter(() if r else ((),))
+def derangements(iterable, r=None):
+ """Yield successive derangements of the elements in *iterable*.
+
+ A derangement is a permutation in which no element appears at its original
+ index. In other words, a derangement is a permutation that has no fixed points.
+
+ Suppose Alice, Bob, Carol, and Dave are playing Secret Santa.
+ The code below outputs all of the different ways to assign gift recipients
+ such that nobody is assigned to himself or herself:
+
+ >>> for d in derangements(['Alice', 'Bob', 'Carol', 'Dave']):
+ ... print(', '.join(d))
+ Bob, Alice, Dave, Carol
+ Bob, Carol, Dave, Alice
+ Bob, Dave, Alice, Carol
+ Carol, Alice, Dave, Bob
+ Carol, Dave, Alice, Bob
+ Carol, Dave, Bob, Alice
+ Dave, Alice, Bob, Carol
+ Dave, Carol, Alice, Bob
+ Dave, Carol, Bob, Alice
+
+ If *r* is given, only the *r*-length derangements are yielded.
+
+ >>> sorted(derangements(range(3), 2))
+ [(1, 0), (1, 2), (2, 0)]
+ >>> sorted(derangements([0, 2, 3], 2))
+ [(2, 0), (2, 3), (3, 0)]
+
+ Elements are treated as unique based on their position, not on their value.
+
+ Consider the Secret Santa example with two *different* people who have
+ the *same* name. Then there are two valid gift assignments even though
+ it might appear that a person is assigned to themselves:
+
+ >>> names = ['Alice', 'Bob', 'Bob']
+ >>> list(derangements(names))
+ [('Bob', 'Bob', 'Alice'), ('Bob', 'Alice', 'Bob')]
+
+ To avoid confusion, make the inputs distinct:
+
+ >>> deduped = [f'{name}{index}' for index, name in enumerate(names)]
+ >>> list(derangements(deduped))
+ [('Bob1', 'Bob2', 'Alice0'), ('Bob2', 'Alice0', 'Bob1')]
+
+ The number of derangements of a set of size *n* is known as the
+ "subfactorial of n". For n > 0, the subfactorial is:
+ ``round(math.factorial(n) / math.e)``.
+
+ References:
+
+ * Article: https://www.numberanalytics.com/blog/ultimate-guide-to-derangements-in-combinatorics
+ * Sizes: https://oeis.org/A000166
+ """
+ xs = tuple(iterable)
+ ys = tuple(range(len(xs)))
+ return compress(
+ permutations(xs, r=r),
+ map(all, map(map, repeat(is_not), repeat(ys), permutations(ys, r=r))),
+ )
+
+
def intersperse(e, iterable, n=1):
"""Intersperse filler element *e* among the items in *iterable*, leaving
*n* items between each filler element.
@@ -932,10 +1012,10 @@ def windowed(seq, n, fillvalue=None, step=1):
if step < 1:
raise ValueError('step must be >= 1')
- iterable = iter(seq)
+ iterator = iter(seq)
# Generate first window
- window = deque(islice(iterable, n), maxlen=n)
+ window = deque(islice(iterator, n), maxlen=n)
# Deal with the first window not being full
if not window:
@@ -948,7 +1028,7 @@ def windowed(seq, n, fillvalue=None, step=1):
# Create the filler for the next windows. The padding ensures
# we have just enough elements to fill the last window.
padding = (fillvalue,) * (n - 1 if step >= n else step - 1)
- filler = map(window.append, chain(iterable, padding))
+ filler = map(window.append, chain(iterator, padding))
# Generate the rest of the windows
for _ in islice(filler, step - 1, None, step):
@@ -969,7 +1049,7 @@ def substrings(iterable):
"""
# The length-1 substrings
seq = []
- for item in iter(iterable):
+ for item in iterable:
seq.append(item)
yield (item,)
seq = tuple(seq)
@@ -1178,8 +1258,10 @@ def interleave_longest(*iterables):
is large).
"""
- i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker))
- return (x for x in i if x is not _marker)
+ for xs in zip_longest(*iterables, fillvalue=_marker):
+ for x in xs:
+ if x is not _marker:
+ yield x
def interleave_evenly(iterables, lengths=None):
@@ -1248,6 +1330,29 @@ def interleave_evenly(iterables, lengths=None):
errors[i] += delta_primary
+def interleave_randomly(*iterables):
+ """Repeatedly select one of the input *iterables* at random and yield the next
+ item from it.
+
+ >>> iterables = [1, 2, 3], 'abc', (True, False, None)
+ >>> list(interleave_randomly(*iterables)) # doctest: +SKIP
+ ['a', 'b', 1, 'c', True, False, None, 2, 3]
+
+ The relative order of the items in each input iterable will preserved. Note the
+ sequences of items with this property are not equally likely to be generated.
+
+ """
+ iterators = [iter(e) for e in iterables]
+ while iterators:
+ idx = randrange(len(iterators))
+ try:
+ yield next(iterators[idx])
+ except StopIteration:
+ # equivalent to `list.pop` but slightly faster
+ iterators[idx] = iterators[-1]
+ del iterators[-1]
+
+
def collapse(iterable, base_type=None, levels=None):
"""Flatten an iterable with multiple levels of nesting (e.g., a list of
lists of tuples) into non-iterable types.
@@ -1398,7 +1503,7 @@ def sliced(seq, n, strict=False):
raise ValueError("seq is not divisible by n.")
yield _slice
- return iter(ret())
+ return ret()
else:
return iterator
@@ -1473,7 +1578,7 @@ def split_before(iterable, pred, maxsplit=-1):
if pred(item) and buf:
yield buf
if maxsplit == 1:
- yield [item] + list(it)
+ yield [item, *it]
return
buf = []
maxsplit -= 1
@@ -1554,7 +1659,7 @@ def split_when(iterable, pred, maxsplit=-1):
if pred(cur_item, next_item):
yield buf
if maxsplit == 1:
- yield [next_item] + list(it)
+ yield [next_item, *it]
return
buf = []
maxsplit -= 1
@@ -1579,7 +1684,7 @@ def split_into(iterable, sizes):
[[1, 2], [3, 4, 5]]
If the sum of *sizes* is larger than the length of *iterable*, fewer items
- will be returned in the iteration that overruns *iterable* and further
+ will be returned in the iteration that overruns the *iterable* and further
lists will be empty:
>>> list(split_into([1,2,3,4], [1,2,3,4]))
@@ -1634,25 +1739,25 @@ def padded(iterable, fillvalue=None, n=None, next_multiple=False):
[1, 2, 3, 4, 5]
"""
- iterable = iter(iterable)
- iterable_with_repeat = chain(iterable, repeat(fillvalue))
+ iterator = iter(iterable)
+ iterator_with_repeat = chain(iterator, repeat(fillvalue))
if n is None:
- return iterable_with_repeat
+ return iterator_with_repeat
elif n < 1:
raise ValueError('n must be at least 1')
elif next_multiple:
def slice_generator():
- for first in iterable:
+ for first in iterator:
yield (first,)
- yield islice(iterable_with_repeat, n - 1)
+ yield islice(iterator_with_repeat, n - 1)
# While elements exist produce slices of size n
return chain.from_iterable(slice_generator())
else:
# Ensure the first batch is at least size n then iterate
- return chain(islice(iterable_with_repeat, n), iterable)
+ return chain(islice(iterator_with_repeat, n), iterator)
def repeat_each(iterable, n=2):
@@ -1749,7 +1854,7 @@ def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None):
def zip_equal(*iterables):
- """``zip`` the input *iterables* together, but raise
+ """``zip`` the input *iterables* together but raise
``UnequalIterablesError`` if they aren't all the same length.
>>> it_1 = range(3)
@@ -1911,7 +2016,7 @@ def unzip(iterable):
:func:`itertools.tee` and thus may require significant storage.
"""
- head, iterable = spy(iter(iterable))
+ head, iterable = spy(iterable)
if not head:
# empty iterable, e.g. zip([], [], [])
return ()
@@ -1919,25 +2024,17 @@ def unzip(iterable):
head = head[0]
iterables = tee(iterable, len(head))
- def itemgetter(i):
- def getter(obj):
- try:
- return obj[i]
- except IndexError:
- # basically if we have an iterable like
- # iter([(1, 2, 3), (4, 5), (6,)])
- # the second unzipped iterable would fail at the third tuple
- # since it would try to access tup[1]
- # same with the third unzipped iterable and the second tuple
- # to support these "improperly zipped" iterables,
- # we create a custom itemgetter
- # which just stops the unzipped iterables
- # at first length mismatch
- raise StopIteration
-
- return getter
-
- return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables))
+ # If we have an iterable like iter([(1, 2, 3), (4, 5), (6,)]),
+ # the second unzipped iterable fails at the third tuple since
+ # it tries to access (6,)[1].
+ # Same with the third unzipped iterable and the second tuple.
+ # To support these "improperly zipped" iterables, we suppress
+ # the IndexError, which just stops the unzipped iterables at
+ # first length mismatch.
+ return tuple(
+ iter_suppress(map(itemgetter(i), it), IndexError)
+ for i, it in enumerate(iterables)
+ )
def divide(n, iterable):
@@ -2162,7 +2259,7 @@ class numeric_range(abc.Sequence, abc.Hashable):
>>> list(numeric_range(3, -1, -1.0))
[3.0, 2.0, 1.0, 0.0]
- Be aware of the limitations of floating point numbers; the representation
+ Be aware of the limitations of floating-point numbers; the representation
of the yielded numbers may be surprising.
``datetime.datetime`` objects can be used for *start* and *stop*, if *step*
@@ -2349,11 +2446,11 @@ def count_cycle(iterable, n=None):
[(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]
"""
- iterable = tuple(iterable)
- if not iterable:
+ seq = tuple(iterable)
+ if not seq:
return iter(())
counter = count() if n is None else range(n)
- return ((i, item) for i in counter for item in iterable)
+ return zip(repeat_each(counter, len(seq)), cycle(seq))
def mark_ends(iterable):
@@ -2377,20 +2474,13 @@ def mark_ends(iterable):
300
"""
it = iter(iterable)
-
- try:
- b = next(it)
- except StopIteration:
- return
-
- try:
- for i in count():
+ for a in it:
+ first = True
+ for b in it:
+ yield first, False, a
a = b
- b = next(it)
- yield i == 0, False, a
-
- except StopIteration:
- yield i == 0, True, a
+ first = False
+ yield first, True, a
def locate(iterable, pred=bool, window_size=None):
@@ -2442,7 +2532,7 @@ def locate(iterable, pred=bool, window_size=None):
def longest_common_prefix(iterables):
- """Yield elements of the longest common prefix amongst given *iterables*.
+ """Yield elements of the longest common prefix among given *iterables*.
>>> ''.join(longest_common_prefix(['abcd', 'abc', 'abf']))
'ab'
@@ -2516,8 +2606,8 @@ class islice_extended:
"""An extension of :func:`itertools.islice` that supports negative values
for *stop*, *start*, and *step*.
- >>> iterable = iter('abcdefgh')
- >>> list(islice_extended(iterable, -4, -1))
+ >>> iterator = iter('abcdefgh')
+ >>> list(islice_extended(iterator, -4, -1))
['e', 'f', 'g']
Slices with negative values require some caching of *iterable*, but this
@@ -2531,8 +2621,8 @@ class islice_extended:
You can also use slice notation directly:
- >>> iterable = map(str, count())
- >>> it = islice_extended(iterable)[10:20:2]
+ >>> iterator = map(str, count())
+ >>> it = islice_extended(iterator)[10:20:2]
>>> list(it)
['10', '12', '14', '16', '18']
@@ -2541,19 +2631,19 @@ class islice_extended:
def __init__(self, iterable, *args):
it = iter(iterable)
if args:
- self._iterable = _islice_helper(it, slice(*args))
+ self._iterator = _islice_helper(it, slice(*args))
else:
- self._iterable = it
+ self._iterator = it
def __iter__(self):
return self
def __next__(self):
- return next(self._iterable)
+ return next(self._iterator)
def __getitem__(self, key):
if isinstance(key, slice):
- return islice_extended(_islice_helper(self._iterable, key))
+ return islice_extended(_islice_helper(self._iterator, key))
raise TypeError('islice_extended.__getitem__ argument must be a slice')
@@ -2589,8 +2679,15 @@ def _islice_helper(it, s):
if n <= 0:
return
- for index, item in islice(cache, 0, n, step):
- yield item
+ for index in range(n):
+ if index % step == 0:
+ # pop and yield the item.
+ # We don't want to use an intermediate variable
+ # it would extend the lifetime of the current item
+ yield cache.popleft()[1]
+ else:
+ # just pop and discard the item
+ cache.popleft()
elif (stop is not None) and (stop < 0):
# Advance to the start position
next(islice(it, start, start), None)
@@ -2600,9 +2697,14 @@ def _islice_helper(it, s):
cache = deque(islice(it, -stop), maxlen=-stop)
for index, item in enumerate(it):
- cached_item = cache.popleft()
if index % step == 0:
- yield cached_item
+ # pop and yield the item.
+ # We don't want to use an intermediate variable
+ # it would extend the lifetime of the current item
+ yield cache.popleft()
+ else:
+ # just pop and discard the item
+ cache.popleft()
cache.append(item)
else:
# When both start and stop are positive we have the normal case
@@ -2672,7 +2774,7 @@ def always_reversible(iterable):
return reversed(list(iterable))
-def consecutive_groups(iterable, ordering=lambda x: x):
+def consecutive_groups(iterable, ordering=None):
"""Yield groups of consecutive items using :func:`itertools.groupby`.
The *ordering* function determines whether two items are adjacent by
returning their position.
@@ -2689,12 +2791,11 @@ def consecutive_groups(iterable, ordering=lambda x: x):
[30, 31, 32, 33]
[40]
- For finding runs of adjacent letters, try using the :meth:`index` method
- of a string of letters:
+ To find runs of adjacent letters, apply :func:`ord` function
+ to convert letters to ordinals.
- >>> from string import ascii_lowercase
>>> iterable = 'abcdfgilmnop'
- >>> ordering = ascii_lowercase.index
+ >>> ordering = ord
>>> for group in consecutive_groups(iterable, ordering):
... print(list(group))
['a', 'b', 'c', 'd']
@@ -2714,9 +2815,12 @@ def consecutive_groups(iterable, ordering=lambda x: x):
[[1, 2], [11, 12], [21, 22]]
"""
- for k, g in groupby(
- enumerate(iterable), key=lambda x: x[0] - ordering(x[1])
- ):
+ if ordering is None:
+ key = lambda x: x[0] - x[1]
+ else:
+ key = lambda x: x[0] - ordering(x[1])
+
+ for k, g in groupby(enumerate(iterable), key=key):
yield map(itemgetter(1), g)
@@ -3162,13 +3266,19 @@ def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None):
dictionary.
"""
- valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc
ret = defaultdict(list)
- for item in iterable:
- key = keyfunc(item)
- value = valuefunc(item)
- ret[key].append(value)
+
+ if valuefunc is None:
+ for item in iterable:
+ key = keyfunc(item)
+ ret[key].append(item)
+
+ else:
+ for item in iterable:
+ key = keyfunc(item)
+ value = valuefunc(item)
+ ret[key].append(value)
if reducefunc is not None:
for key, value_list in ret.items():
@@ -3190,9 +3300,9 @@ def rlocate(iterable, pred=bool, window_size=None):
Set *pred* to a custom function to, e.g., find the indexes for a particular
item:
- >>> iterable = iter('abcb')
+ >>> iterator = iter('abcb')
>>> pred = lambda x: x == 'b'
- >>> list(rlocate(iterable, pred))
+ >>> list(rlocate(iterator, pred))
[3, 1]
If *window_size* is given, then the *pred* function will be called with
@@ -3258,7 +3368,7 @@ def replace(iterable, pred, substitutes, count=None, window_size=1):
# Add padding such that the number of windows matches the length of the
# iterable
- it = chain(iterable, [_marker] * (window_size - 1))
+ it = chain(iterable, repeat(_marker, window_size - 1))
windows = windowed(it, window_size)
n = 0
@@ -3414,7 +3524,7 @@ class time_limited:
if limit_seconds < 0:
raise ValueError('limit_seconds must be positive')
self.limit_seconds = limit_seconds
- self._iterable = iter(iterable)
+ self._iterator = iter(iterable)
self._start_time = monotonic()
self.timed_out = False
@@ -3425,7 +3535,7 @@ class time_limited:
if self.limit_seconds == 0:
self.timed_out = True
raise StopIteration
- item = next(self._iterable)
+ item = next(self._iterator)
if monotonic() - self._start_time > self.limit_seconds:
self.timed_out = True
raise StopIteration
@@ -3470,9 +3580,9 @@ def only(iterable, default=None, too_long=None):
return default
-def _ichunk(iterable, n):
+def _ichunk(iterator, n):
cache = deque()
- chunk = islice(iterable, n)
+ chunk = islice(iterator, n)
def generator():
with suppress(StopIteration):
@@ -3521,10 +3631,10 @@ def ichunked(iterable, n):
[8, 9, 10, 11]
"""
- iterable = iter(iterable)
+ iterator = iter(iterable)
while True:
# Create new chunk
- chunk, materialize_next = _ichunk(iterable, n)
+ chunk, materialize_next = _ichunk(iterator, n)
# Check to see whether we're at the end of the source iterable
if not materialize_next():
@@ -3641,7 +3751,7 @@ def map_except(function, iterable, *exceptions):
pass
-def map_if(iterable, pred, func, func_else=lambda x: x):
+def map_if(iterable, pred, func, func_else=None):
"""Evaluate each item from *iterable* using *pred*. If the result is
equivalent to ``True``, transform the item with *func* and yield it.
Otherwise, transform the item with *func_else* and yield it.
@@ -3659,8 +3769,14 @@ def map_if(iterable, pred, func, func_else=lambda x: x):
... lambda x: f'{sqrt(x):.2f}', lambda x: None))
[None, None, None, None, None, '0.00', '1.00', '1.41', '1.73', '2.00']
"""
- for item in iterable:
- yield func(item) if pred(item) else func_else(item)
+
+ if func_else is None:
+ for item in iterable:
+ yield func(item) if pred(item) else item
+
+ else:
+ for item in iterable:
+ yield func(item) if pred(item) else func_else(item)
def _sample_unweighted(iterator, k, strict):
@@ -3674,7 +3790,7 @@ def _sample_unweighted(iterator, k, strict):
with suppress(StopIteration):
while True:
- W *= exp(log(random()) / k)
+ W *= random() ** (1 / k)
skip = floor(log(random()) / log1p(-W))
element = next(islice(iterator, skip, None))
reservoir[randrange(k)] = element
@@ -3748,7 +3864,7 @@ def _sample_counted(population, k, counts, strict):
with suppress(StopIteration):
W = 1.0
while True:
- W *= exp(log(random()) / k)
+ W *= random() ** (1 / k)
skip = floor(log(random()) / log1p(-W))
element = feed(skip)
reservoir[randrange(k)] = element
@@ -3759,8 +3875,11 @@ def _sample_counted(population, k, counts, strict):
def sample(iterable, k, weights=None, *, counts=None, strict=False):
"""Return a *k*-length list of elements chosen (without replacement)
- from the *iterable*. Similar to :func:`random.sample`, but works on
- iterables of unknown length.
+ from the *iterable*.
+
+ Similar to :func:`random.sample`, but works on inputs that aren't
+ indexable (such as sets and dictionaries) and on inputs where the
+ size isn't known in advance (such as generators).
>>> iterable = range(100)
>>> sample(iterable, 5) # doctest: +SKIP
@@ -3793,7 +3912,26 @@ def sample(iterable, k, weights=None, *, counts=None, strict=False):
By default, the `Algorithm L <https://w.wiki/ANrM>`__ reservoir sampling
technique is used. When *weights* are provided,
- `Algorithm A-ExpJ <https://w.wiki/ANrS>`__ is used.
+ `Algorithm A-ExpJ <https://w.wiki/ANrS>`__ is used instead.
+
+ Notes on reproducibility:
+
+ * The algorithms rely on inexact floating-point functions provided
+ by the underlying math library (e.g. ``log``, ``log1p``, and ``pow``).
+ Those functions can `produce slightly different results
+ <https://members.loria.fr/PZimmermann/papers/accuracy.pdf>`_ on
+ different builds. Accordingly, selections can vary across builds
+ even for the same seed.
+
+ * The algorithms loop over the input and make selections based on
+ ordinal position, so selections from unordered collections (such as
+ sets) won't reproduce across sessions on the same platform using the
+ same seed. For example, this won't reproduce::
+
+ >> seed(8675309)
+ >> sample(set('abcdefghijklmnopqrstuvwxyz'), 10)
+ ['c', 'p', 'e', 'w', 's', 'a', 'j', 'd', 'n', 't']
+
"""
iterator = iter(iterable)
@@ -4379,14 +4517,14 @@ class countable:
"""
def __init__(self, iterable):
- self._it = iter(iterable)
+ self._iterator = iter(iterable)
self.items_seen = 0
def __iter__(self):
return self
def __next__(self):
- item = next(self._it)
+ item = next(self._iterator)
self.items_seen += 1
return item
@@ -4405,15 +4543,15 @@ def chunked_even(iterable, n):
[[1, 2, 3], [4, 5, 6], [7]]
"""
- iterable = iter(iterable)
+ iterator = iter(iterable)
# Initialize a buffer to process the chunks while keeping
# some back to fill any underfilled chunks
min_buffer = (n - 1) * (n - 2)
- buffer = list(islice(iterable, min_buffer))
+ buffer = list(islice(iterator, min_buffer))
# Append items until we have a completed chunk
- for _ in islice(map(buffer.append, iterable), n, None, n):
+ for _ in islice(map(buffer.append, iterator), n, None, n):
yield buffer[:n]
del buffer[:n]
@@ -4624,8 +4762,8 @@ def classify_unique(iterable, key=None):
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.
+ """Returns both the smallest and largest items from an iterable
+ or from two or more arguments.
>>> minmax([3, 1, 5])
(1, 5)
@@ -4647,10 +4785,16 @@ def minmax(iterable_or_value, *others, key=None, default=_marker):
Otherwise ``ValueError`` is raised.
+ This function makes a single pass over the input elements and takes care to
+ minimize the number of comparisons made during processing.
+
+ Note that unlike the builtin ``max`` function, which always returns the first
+ item with the maximum value, this function may return another item when there are
+ ties.
+
This function is based on the
- `recipe <http://code.activestate.com/recipes/577916/>`__ by
- Raymond Hettinger and takes care to minimize the number of comparisons
- performed.
+ `recipe <https://code.activestate.com/recipes/577916-fast-minmax-function>`__ by
+ Raymond Hettinger.
"""
iterable = (iterable_or_value, *others) if others else iterable_or_value
@@ -4936,10 +5080,12 @@ def _complex_sumprod(v1, v2):
Used by :func:`dft` and :func:`idft`.
"""
- r1 = chain((p.real for p in v1), (-p.imag for p in v1))
- r2 = chain((q.real for q in v2), (q.imag for q in v2))
- i1 = chain((p.real for p in v1), (p.imag for p in v1))
- i2 = chain((q.imag for q in v2), (q.real for q in v2))
+ real = attrgetter('real')
+ imag = attrgetter('imag')
+ r1 = chain(map(real, v1), map(neg, map(imag, v1)))
+ r2 = chain(map(real, v2), map(imag, v2))
+ i1 = chain(map(real, v1), map(imag, v1))
+ i2 = chain(map(imag, v2), map(real, v2))
return complex(_fsumprod(r1, r2), _fsumprod(i1, i2))
@@ -4948,11 +5094,16 @@ def dft(xarr):
Yields the components of the corresponding transformed output vector.
>>> import cmath
- >>> xarr = [1, 2-1j, -1j, -1+2j]
- >>> Xarr = [2, -2-2j, -2j, 4+4j]
+ >>> xarr = [1, 2-1j, -1j, -1+2j] # time domain
+ >>> Xarr = [2, -2-2j, -2j, 4+4j] # frequency domain
+ >>> magnitudes, phases = zip(*map(cmath.polar, Xarr))
>>> all(map(cmath.isclose, dft(xarr), Xarr))
True
+ Inputs are restricted to numeric types that can add and multiply
+ with a complex number. This includes int, float, complex, and
+ Fraction, but excludes Decimal.
+
See :func:`idft` for the inverse Discrete Fourier Transform.
"""
N = len(xarr)
@@ -4968,11 +5119,15 @@ def idft(Xarr):
inverse-transformed output vector.
>>> import cmath
- >>> xarr = [1, 2-1j, -1j, -1+2j]
- >>> Xarr = [2, -2-2j, -2j, 4+4j]
+ >>> xarr = [1, 2-1j, -1j, -1+2j] # time domain
+ >>> Xarr = [2, -2-2j, -2j, 4+4j] # frequency domain
>>> all(map(cmath.isclose, idft(Xarr), xarr))
True
+ Inputs are restricted to numeric types that can add and multiply
+ with a complex number. This includes int, float, complex, and
+ Fraction, but excludes Decimal.
+
See :func:`dft` for the Discrete Fourier Transform.
"""
N = len(Xarr)
@@ -5000,21 +5155,149 @@ def doublestarmap(func, iterable):
yield func(**item)
-def _nth_prime_ub(n):
- "Upper bound for the nth prime (counting from 1)."
+def _nth_prime_bounds(n):
+ """Bounds for the nth prime (counting from 1): lb < p_n < ub."""
+ # At and above 688,383, the lb/ub spread is under 0.003 * p_n.
+
+ if n < 1:
+ raise ValueError
+
+ if n < 6:
+ return (n, 2.25 * n)
+
# https://en.wikipedia.org/wiki/Prime-counting_function#Inequalities
- return n * log(n * log(n)) if n >= 6 else 11.1
+ upper_bound = n * log(n * log(n))
+ lower_bound = upper_bound - n
+ if n >= 688_383:
+ upper_bound -= n * (1.0 - (log(log(n)) - 2.0) / log(n))
+
+ return lower_bound, upper_bound
-def nth_prime(n):
+def nth_prime(n, *, approximate=False):
"""Return the nth prime (counting from 0).
>>> nth_prime(0)
2
>>> nth_prime(100)
547
+
+ If *approximate* is set to True, will return a prime close
+ to the nth prime. The estimation is much faster than computing
+ an exact result.
+
+ >>> nth_prime(200_000_000, approximate=True) # Exact result is 4222234763
+ 4217820427
+
"""
- if n < 0:
- raise ValueError
- limit = math.ceil(_nth_prime_ub(n + 1))
- return nth(sieve(limit), n)
+ lb, ub = _nth_prime_bounds(n + 1)
+
+ if not approximate or n <= 1_000_000:
+ return nth(sieve(ceil(ub)), n)
+
+ # Search from the midpoint and return the first odd prime
+ odd = floor((lb + ub) / 2) | 1
+ return first_true(count(odd, step=2), pred=is_prime)
+
+
+def argmin(iterable, *, key=None):
+ """
+ Index of the first occurrence of a minimum value in an iterable.
+
+ >>> argmin('efghabcdijkl')
+ 4
+ >>> argmin([3, 2, 1, 0, 4, 2, 1, 0])
+ 3
+
+ For example, look up a label corresponding to the position
+ of a value that minimizes a cost function::
+
+ >>> def cost(x):
+ ... "Days for a wound to heal given a subject's age."
+ ... return x**2 - 20*x + 150
+ ...
+ >>> labels = ['homer', 'marge', 'bart', 'lisa', 'maggie']
+ >>> ages = [ 35, 30, 10, 9, 1 ]
+
+ # Fastest healing family member
+ >>> labels[argmin(ages, key=cost)]
+ 'bart'
+
+ # Age with fastest healing
+ >>> min(ages, key=cost)
+ 10
+
+ """
+ if key is not None:
+ iterable = map(key, iterable)
+ return min(enumerate(iterable), key=itemgetter(1))[0]
+
+
+def argmax(iterable, *, key=None):
+ """
+ Index of the first occurrence of a maximum value in an iterable.
+
+ >>> argmax('abcdefghabcd')
+ 7
+ >>> argmax([0, 1, 2, 3, 3, 2, 1, 0])
+ 3
+
+ For example, identify the best machine learning model::
+
+ >>> models = ['svm', 'random forest', 'knn', 'naïve bayes']
+ >>> accuracy = [ 68, 61, 84, 72 ]
+
+ # Most accurate model
+ >>> models[argmax(accuracy)]
+ 'knn'
+
+ # Best accuracy
+ >>> max(accuracy)
+ 84
+
+ """
+ if key is not None:
+ iterable = map(key, iterable)
+ return max(enumerate(iterable), key=itemgetter(1))[0]
+
+
+def extract(iterable, indices):
+ """Yield values at the specified indices.
+
+ Example:
+
+ >>> data = 'abcdefghijklmnopqrstuvwxyz'
+ >>> list(extract(data, [7, 4, 11, 11, 14]))
+ ['h', 'e', 'l', 'l', 'o']
+
+ The *iterable* is consumed lazily and can be infinite.
+ The *indices* are consumed immediately and must be finite.
+
+ Raises ``IndexError`` if an index lies beyond the iterable.
+ Raises ``ValueError`` for negative indices.
+ """
+
+ iterator = iter(iterable)
+ index_and_position = sorted(zip(indices, count()))
+
+ if index_and_position and index_and_position[0][0] < 0:
+ raise ValueError('Indices must be non-negative')
+
+ buffer = {}
+ iterator_position = -1
+ next_to_emit = 0
+
+ for index, order in index_and_position:
+ advance = index - iterator_position
+ if advance:
+ try:
+ value = next(islice(iterator, advance - 1, None))
+ except StopIteration:
+ raise IndexError(index)
+ iterator_position = index
+
+ buffer[order] = value
+
+ while next_to_emit in buffer:
+ yield buffer.pop(next_to_emit)
+ next_to_emit += 1
diff --git a/contrib/python/more-itertools/py3/more_itertools/more.pyi b/contrib/python/more-itertools/py3/more_itertools/more.pyi
index dea9abc1cd1..b5e33f8b747 100644
--- a/contrib/python/more-itertools/py3/more_itertools/more.pyi
+++ b/contrib/python/more-itertools/py3/more_itertools/more.pyi
@@ -34,6 +34,8 @@ __all__ = [
'all_unique',
'always_iterable',
'always_reversible',
+ 'argmax',
+ 'argmin',
'bucket',
'callback_iter',
'chunked',
@@ -47,6 +49,7 @@ __all__ = [
'consumer',
'count_cycle',
'countable',
+ 'derangements',
'dft',
'difference',
'distinct_combinations',
@@ -58,6 +61,7 @@ __all__ = [
'duplicates_justseen',
'classify_unique',
'exactly_n',
+ 'extract',
'filter_except',
'filter_map',
'first',
@@ -70,6 +74,7 @@ __all__ = [
'interleave',
'interleave_evenly',
'interleave_longest',
+ 'interleave_randomly',
'intersperse',
'is_sorted',
'islice_extended',
@@ -222,6 +227,9 @@ def strictly_n(
def distinct_permutations(
iterable: Iterable[_T], r: int | None = ...
) -> Iterator[tuple[_T, ...]]: ...
+def derangements(
+ iterable: Iterable[_T], r: int | None = None
+) -> Iterator[tuple[_T, ...]]: ...
def intersperse(
e: _U, iterable: Iterable[_T], n: int = ...
) -> Iterator[_T | _U]: ...
@@ -258,6 +266,7 @@ def interleave_longest(*iterables: Iterable[_T]) -> Iterator[_T]: ...
def interleave_evenly(
iterables: list[Iterable[_T]], lengths: list[int] | None = ...
) -> Iterator[_T]: ...
+def interleave_randomly(*iterables: Iterable[_T]) -> Iterable[_T]: ...
def collapse(
iterable: Iterable[Any],
base_type: _ClassInfo | None = ...,
@@ -559,7 +568,7 @@ class islice_extended(Generic[_T], Iterator[_T]):
def always_reversible(iterable: Iterable[_T]) -> Iterator[_T]: ...
def consecutive_groups(
- iterable: Iterable[_T], ordering: Callable[[_T], int] = ...
+ iterable: Iterable[_T], ordering: None | Callable[[_T], int] = ...
) -> Iterator[Iterator[_T]]: ...
@overload
def difference(
@@ -928,4 +937,13 @@ def doublestarmap(
def dft(xarr: Sequence[complex]) -> Iterator[complex]: ...
def idft(Xarr: Sequence[complex]) -> Iterator[complex]: ...
def _nth_prime_ub(n: int) -> float: ...
-def nth_prime(n: int) -> int: ...
+def nth_prime(n: int, *, approximate: bool = ...) -> int: ...
+def argmin(
+ iterable: Iterable[_T], *, key: Callable[[_T], _U] | None = ...
+) -> int: ...
+def argmax(
+ iterable: Iterable[_T], *, key: Callable[[_T], _U] | None = ...
+) -> int: ...
+def extract(
+ iterable: Iterable[_T], indices: Iterable[int]
+) -> Iterator[_T]: ...
diff --git a/contrib/python/more-itertools/py3/more_itertools/recipes.py b/contrib/python/more-itertools/py3/more_itertools/recipes.py
index 51bb93b1efc..dacf61407d4 100644
--- a/contrib/python/more-itertools/py3/more_itertools/recipes.py
+++ b/contrib/python/more-itertools/py3/more_itertools/recipes.py
@@ -10,10 +10,11 @@ Some backward-compatible usability improvements have been made.
import random
+from bisect import bisect_left, insort
from collections import deque
from contextlib import suppress
-from collections.abc import Sized
-from functools import lru_cache, partial
+from functools import lru_cache, partial, reduce
+from heapq import heappush, heappushpop
from itertools import (
accumulate,
chain,
@@ -26,11 +27,12 @@ from itertools import (
product,
repeat,
starmap,
+ takewhile,
tee,
zip_longest,
)
from math import prod, comb, isqrt, gcd
-from operator import mul, not_, itemgetter, getitem
+from operator import mul, not_, itemgetter, getitem, index
from random import randrange, sample, choice
from sys import hexversion
@@ -71,6 +73,7 @@ __all__ = [
'random_product',
'repeatfunc',
'roundrobin',
+ 'running_median',
'sieve',
'sliding_window',
'subslices',
@@ -92,19 +95,28 @@ _marker = object()
# zip with strict is available for Python 3.10+
try:
zip(strict=True)
-except TypeError:
+except TypeError: # pragma: no cover
_zip_strict = zip
-else:
+else: # pragma: no cover
_zip_strict = partial(zip, strict=True)
# math.sumprod is available for Python 3.12+
try:
from math import sumprod as _sumprod
-except ImportError:
+except ImportError: # pragma: no cover
_sumprod = lambda x, y: dotproduct(x, y)
+# heapq max-heap functions are available for Python 3.14+
+try:
+ from heapq import heappush_max, heappushpop_max
+except ImportError: # pragma: no cover
+ _max_heap_available = False
+else: # pragma: no cover
+ _max_heap_available = True
+
+
def take(n, iterable):
"""Return first *n* items of the *iterable* as a list.
@@ -147,14 +159,12 @@ def tail(n, iterable):
['E', 'F', 'G']
"""
- # If the given iterable has a length, then we can use islice to get its
- # final elements. Note that if the iterable is not actually Iterable,
- # either islice or deque will throw a TypeError. This is why we don't
- # check if it is Iterable.
- if isinstance(iterable, Sized):
- return islice(iterable, max(0, len(iterable) - n), None)
- else:
+ try:
+ size = len(iterable)
+ except TypeError:
return iter(deque(iterable, maxlen=n))
+ else:
+ return islice(iterable, max(0, size - n), None)
def consume(iterator, n=None):
@@ -341,9 +351,9 @@ def _pairwise(iterable):
try:
from itertools import pairwise as itertools_pairwise
-except ImportError:
+except ImportError: # pragma: no cover
pairwise = _pairwise
-else:
+else: # pragma: no cover
def pairwise(iterable):
return itertools_pairwise(iterable)
@@ -635,7 +645,7 @@ def random_product(*args, repeat=1):
('a', 2, 'd', 3)
This equivalent to taking a random selection from
- ``itertools.product(*args, **kwarg)``.
+ ``itertools.product(*args, repeat=repeat)``.
"""
pools = [tuple(pool) for pool in args] * repeat
@@ -807,23 +817,9 @@ def before_and_after(predicate, it):
Note that the first iterator must be fully consumed before the second
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
-
- # Note: this is different from itertools recipes to allow nesting
- # before_and_after remainders into before_and_after again. See tests
- # for an example.
- remainder_iterator = chain(transition, it)
-
- return true_iterator(), remainder_iterator
+ trues, after = tee(it)
+ trues = compress(takewhile(predicate, trues), zip(after))
+ return trues, after
def triplewise(iterable):
@@ -833,7 +829,7 @@ def triplewise(iterable):
[('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E')]
"""
- # This deviates from the itertools documentation reciple - see
+ # This deviates from the itertools documentation recipe - see
# https://github.com/more-itertools/more-itertools/issues/889
t1, t2, t3 = tee(iterable, 3)
next(t3, None)
@@ -905,11 +901,15 @@ def polynomial_from_roots(roots):
>>> polynomial_from_roots(roots) # x³ - 4 x² - 17 x + 60
[1, -4, -17, 60]
+ Note that polynomial coefficients are specified in descending power order.
+
Supports all numeric types: int, float, complex, Decimal, Fraction.
"""
+
# This recipe differs from the one in itertools docs in that it
# applies list() after each call to convolve(). This avoids
# hitting stack limits with nested generators.
+
poly = [1]
for root in roots:
poly = list(convolve(poly, (1, -root)))
@@ -978,7 +978,7 @@ def sieve(n):
yield from iter_index(data, 1, start)
-def _batched(iterable, n, *, strict=False):
+def _batched(iterable, n, *, strict=False): # pragma: no cover
"""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``.
@@ -1004,10 +1004,9 @@ if hexversion >= 0x30D00A2: # pragma: no cover
def batched(iterable, n, *, strict=False):
return itertools_batched(iterable, n, strict=strict)
-else:
- batched = _batched
-
batched.__doc__ = _batched.__doc__
+else: # pragma: no cover
+ batched = _batched
def transpose(it):
@@ -1022,15 +1021,68 @@ 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*.
+def _is_scalar(value, stringlike=(str, bytes)):
+ "Scalars are bytes, strings, and non-iterables."
+ try:
+ iter(value)
+ except TypeError:
+ return True
+ return isinstance(value, stringlike)
+
+
+def _flatten_tensor(tensor):
+ "Depth-first iterator over scalars in a tensor."
+ iterator = iter(tensor)
+ while True:
+ try:
+ value = next(iterator)
+ except StopIteration:
+ return iterator
+ iterator = chain((value,), iterator)
+ if _is_scalar(value):
+ return iterator
+ iterator = chain.from_iterable(iterator)
+
+
+def reshape(matrix, shape):
+ """Change the shape of a *matrix*.
+
+ If *shape* is an integer, the matrix must be two dimensional
+ and the shape is interpreted as the desired number of columns:
+
+ >>> matrix = [(0, 1), (2, 3), (4, 5)]
+ >>> cols = 3
+ >>> list(reshape(matrix, cols))
+ [(0, 1, 2), (3, 4, 5)]
+
+ If *shape* is a tuple (or other iterable), the input matrix can have
+ any number of dimensions. It will first be flattened and then rebuilt
+ to the desired shape which can also be multidimensional:
+
+ >>> matrix = [(0, 1), (2, 3), (4, 5)] # Start with a 3 x 2 matrix
+
+ >>> list(reshape(matrix, (2, 3))) # Make a 2 x 3 matrix
+ [(0, 1, 2), (3, 4, 5)]
+
+ >>> list(reshape(matrix, (6,))) # Make a vector of length six
+ [0, 1, 2, 3, 4, 5]
+
+ >>> list(reshape(matrix, (2, 1, 3, 1))) # Make 2 x 1 x 3 x 1 tensor
+ [(((0,), (1,), (2,)),), (((3,), (4,), (5,)),)]
+
+ Each dimension is assumed to be uniform, either all arrays or all scalars.
+ Flattening stops when the first value in a dimension is a scalar.
+ Scalars are bytes, strings, and non-iterables.
+ The reshape iterator stops when the requested shape is complete
+ or when the input is exhausted, whichever comes first.
- >>> 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)
+ if isinstance(shape, int):
+ return batched(chain.from_iterable(matrix), shape)
+ first_dim, *dims = shape
+ scalar_stream = _flatten_tensor(matrix)
+ reshaped = reduce(batched, reversed(dims), scalar_stream)
+ return islice(reshaped, first_dim)
def matmul(m1, m2):
@@ -1061,7 +1113,7 @@ def _factor_pollard(n):
d = gcd(x - y, n)
if d != n:
return d
- raise ValueError('prime or under 5')
+ raise ValueError('prime or under 5') # pragma: no cover
_primes_below_211 = tuple(sieve(211))
@@ -1112,6 +1164,8 @@ def polynomial_eval(coefficients, x):
>>> polynomial_eval(coefficients, x)
8.125
+ Note that polynomial coefficients are specified in descending power order.
+
Supports all numeric types: int, float, complex, Decimal, Fraction.
"""
n = len(coefficients)
@@ -1142,6 +1196,8 @@ def polynomial_derivative(coefficients):
>>> derivative_coefficients
[3, -8, -17]
+ Note that polynomial coefficients are specified in descending power order.
+
Supports all numeric types: int, float, complex, Decimal, Fraction.
"""
n = len(coefficients)
@@ -1310,18 +1366,106 @@ def multinomial(*counts):
>>> from collections import Counter
>>> list(Counter('abracadabra').values())
[5, 2, 2, 1, 1]
- >>> multinomial(5, 2, 1, 1, 2)
+ >>> multinomial(5, 2, 2, 1, 1)
83160
A binomial coefficient is a special case of multinomial where there are
only two categories. For example, the number of ways to arrange 12 balls
with 5 reds and 7 blues is ``multinomial(5, 7)`` or ``math.comb(12, 5)``.
- When the multiplicities are all just 1, :func:`multinomial`
- is a special case of ``math.factorial`` so that
+ Likewise, factorial is a special case of multinomial where
+ the multiplicities are all just 1 so that
``multinomial(1, 1, 1, 1, 1, 1, 1) == math.factorial(7)``.
Reference: https://en.wikipedia.org/wiki/Multinomial_theorem
"""
return prod(map(comb, accumulate(counts), counts))
+
+
+def _running_median_minheap_and_maxheap(iterator): # pragma: no cover
+ "Non-windowed running_median() for Python 3.14+"
+
+ read = iterator.__next__
+ lo = [] # max-heap
+ hi = [] # min-heap (same size as or one smaller than lo)
+
+ with suppress(StopIteration):
+ while True:
+ heappush_max(lo, heappushpop(hi, read()))
+ yield lo[0]
+
+ heappush(hi, heappushpop_max(lo, read()))
+ yield (lo[0] + hi[0]) / 2
+
+
+def _running_median_minheap_only(iterator): # pragma: no cover
+ "Backport of non-windowed running_median() for Python 3.13 and prior."
+
+ read = iterator.__next__
+ lo = [] # max-heap (actually a minheap with negated values)
+ hi = [] # min-heap (same size as or one smaller than lo)
+
+ with suppress(StopIteration):
+ while True:
+ heappush(lo, -heappushpop(hi, read()))
+ yield -lo[0]
+
+ heappush(hi, -heappushpop(lo, -read()))
+ yield (hi[0] - lo[0]) / 2
+
+
+def _running_median_windowed(iterator, maxlen):
+ "Yield median of values in a sliding window."
+
+ window = deque()
+ ordered = []
+
+ for x in iterator:
+ window.append(x)
+ insort(ordered, x)
+
+ if len(ordered) > maxlen:
+ i = bisect_left(ordered, window.popleft())
+ del ordered[i]
+
+ n = len(ordered)
+ m = n // 2
+ yield ordered[m] if n & 1 else (ordered[m - 1] + ordered[m]) / 2
+
+
+def running_median(iterable, *, maxlen=None):
+ """Cumulative median of values seen so far or values in a sliding window.
+
+ Set *maxlen* to a positive integer to specify the maximum size
+ of the sliding window. The default of *None* is equivalent to
+ an unbounded window.
+
+ For example:
+
+ >>> list(running_median([5.0, 9.0, 4.0, 12.0, 8.0, 9.0]))
+ [5.0, 7.0, 5.0, 7.0, 8.0, 8.5]
+ >>> list(running_median([5.0, 9.0, 4.0, 12.0, 8.0, 9.0], maxlen=3))
+ [5.0, 7.0, 5.0, 9.0, 8.0, 9.0]
+
+ Supports numeric types such as int, float, Decimal, and Fraction,
+ but not complex numbers which are unorderable.
+
+ On version Python 3.13 and prior, max-heaps are simulated with
+ negative values. The negation causes Decimal inputs to apply context
+ rounding, making the results slightly different than that obtained
+ by statistics.median().
+ """
+
+ iterator = iter(iterable)
+
+ if maxlen is not None:
+ maxlen = index(maxlen)
+ if maxlen <= 0:
+ raise ValueError('Window size should be positive')
+ return _running_median_windowed(iterator, maxlen)
+
+ if not _max_heap_available:
+ return _running_median_minheap_only(iterator) # pragma: no cover
+
+ return _running_median_minheap_and_maxheap(iterator) # pragma: no cover
diff --git a/contrib/python/more-itertools/py3/more_itertools/recipes.pyi b/contrib/python/more-itertools/py3/more_itertools/recipes.pyi
index 0d50ca63a39..de3d0a1777a 100644
--- a/contrib/python/more-itertools/py3/more_itertools/recipes.pyi
+++ b/contrib/python/more-itertools/py3/more_itertools/recipes.pyi
@@ -3,6 +3,8 @@
from __future__ import annotations
from collections.abc import Iterable, Iterator, Sequence
+from decimal import Decimal
+from fractions import Fraction
from typing import (
Any,
Callable,
@@ -47,6 +49,7 @@ __all__ = [
'random_product',
'repeatfunc',
'roundrobin',
+ 'running_median',
'sieve',
'sliding_window',
'subslices',
@@ -67,6 +70,7 @@ _T = TypeVar('_T')
_T1 = TypeVar('_T1')
_T2 = TypeVar('_T2')
_U = TypeVar('_U')
+_NumberT = TypeVar("_NumberT", float, Decimal, Fraction)
def take(n: int, iterable: Iterable[_T]) -> list[_T]: ...
def tabulate(
@@ -168,15 +172,21 @@ def iter_index(
stop: int | None = ...,
) -> Iterator[int]: ...
def sieve(n: int) -> Iterator[int]: ...
-def batched(
+def _batched(
iterable: Iterable[_T], n: int, *, strict: bool = False
-) -> Iterator[tuple[_T]]: ...
+) -> Iterator[tuple[_T, ...]]: ...
+
+batched = _batched
+
def transpose(
it: Iterable[Iterable[_T]],
) -> Iterator[tuple[_T, ...]]: ...
+@overload
def reshape(
- matrix: Iterable[Iterable[_T]], cols: int
+ matrix: Iterable[Iterable[_T]], shape: int
) -> Iterator[tuple[_T, ...]]: ...
+@overload
+def reshape(matrix: Iterable[Any], shape: Iterable[int]) -> Iterator[Any]: ...
def matmul(m1: Sequence[_T], m2: Sequence[_T]) -> Iterator[tuple[_T]]: ...
def _factor_trial(n: int) -> Iterator[int]: ...
def _factor_pollard(n: int) -> int: ...
@@ -190,3 +200,6 @@ def _strong_probable_prime(n: int, base: int) -> bool: ...
def is_prime(n: int) -> bool: ...
def loops(n: int) -> Iterator[None]: ...
def multinomial(*counts: int) -> int: ...
+def running_median(
+ iterable: Iterable[_NumberT], *, maxlen: int | None = ...
+) -> Iterator[_NumberT]: ...
diff --git a/contrib/python/more-itertools/py3/tests/test_more.py b/contrib/python/more-itertools/py3/tests/test_more.py
index f30e747d190..d608334b11f 100644
--- a/contrib/python/more-itertools/py3/tests/test_more.py
+++ b/contrib/python/more-itertools/py3/tests/test_more.py
@@ -1,6 +1,11 @@
+from __future__ import annotations
+
import cmath
+import gc
+import platform
+import weakref
-from collections import Counter, abc
+from collections import Counter, abc, deque
from collections.abc import Set
from datetime import datetime, timedelta
from decimal import Decimal
@@ -28,6 +33,7 @@ from statistics import mean
from string import ascii_letters
from sys import version_info
from time import sleep
+from typing import Iterable, Iterator, NamedTuple
from unittest import skipIf, TestCase
import more_itertools as mi
@@ -174,6 +180,16 @@ class LastTests(TestCase):
with self.assertRaises(ValueError):
mi.last(iterable)
+ def test_reversed_is_none(self):
+ # See https://github.com/more-itertools/more-itertools/issues/1001
+ class ReversedIsNone:
+ __reversed__ = None
+
+ def __iter__(self):
+ return iter([1])
+
+ self.assertEqual(mi.last(ReversedIsNone()), 1)
+
class NthOrLastTests(TestCase):
"""Tests for ``nth_or_last()``"""
@@ -528,6 +544,88 @@ class DistinctPermutationsTests(TestCase):
self.assertCountEqual(actual, expected)
+class DerangementsTests(TestCase):
+ def test_unique_values(self):
+ n = 8
+ expected = set(
+ x
+ for x in permutations(range(n))
+ if not any(x[i] == i for i in range(n))
+ )
+ for i, iterable in enumerate(
+ [
+ range(n),
+ list(range(n)),
+ set(range(n)),
+ ]
+ ):
+ actual = set(mi.derangements(iterable))
+ self.assertEqual(actual, expected)
+
+ def test_repeated_values(self):
+ self.assertEqual(
+ [''.join(x) for x in mi.derangements('AACD')],
+ [
+ 'AADC',
+ 'ACDA',
+ 'ADAC',
+ 'CADA',
+ 'CDAA',
+ 'CDAA',
+ 'DAAC',
+ 'DCAA',
+ 'DCAA',
+ ],
+ )
+
+ def test_unsortable_unhashable(self):
+ iterable = (0, True, ['Carol'])
+ actual = list(mi.derangements(iterable))
+ expected = [(True, ['Carol'], 0), (['Carol'], 0, True)]
+ self.assertListEqual(actual, expected)
+
+ def test_r(self):
+ s = 'ABCD'
+ for r, expected in [
+ (0, ['']),
+ (1, ['B', 'C', 'D']),
+ (2, ['BA', 'BC', 'BD', 'CA', 'CD', 'DA', 'DC']),
+ (
+ 3,
+ [
+ 'BAD',
+ 'BCA',
+ 'BCD',
+ 'BDA',
+ 'CAB',
+ 'CAD',
+ 'CDA',
+ 'CDB',
+ 'DAB',
+ 'DCA',
+ 'DCB',
+ ],
+ ),
+ (
+ 4,
+ [
+ 'BADC',
+ 'BCDA',
+ 'BDAC',
+ 'CADB',
+ 'CDAB',
+ 'CDBA',
+ 'DABC',
+ 'DCAB',
+ 'DCBA',
+ ],
+ ),
+ ]:
+ with self.subTest(r=r):
+ actual = [''.join(x) for x in mi.derangements(s, r=r)]
+ self.assertEqual(actual, expected)
+
+
class IlenTests(TestCase):
def test_ilen(self):
"""Sanity-checks for ``ilen()``."""
@@ -1116,6 +1214,42 @@ class InterleaveEvenlyTests(TestCase):
list(mi.interleave_evenly(iterables, lengths=lengths))
+class InterleaveRandomlyTests(TestCase):
+ def test_basic(self):
+ seed(0) # For reproducibility
+ iterables = [1, 2, 3], 'abc', (True, False, None)
+ self.assertEqual(
+ list(mi.interleave_randomly(*iterables)),
+ ['a', 'b', 1, 'c', True, False, None, 2, 3],
+ )
+
+ def test_some_empty(self):
+ self.assertEqual(
+ list(mi.interleave_randomly([1, 2, 3], [], [])),
+ [1, 2, 3],
+ )
+ self.assertEqual(
+ list(mi.interleave_randomly([], [1, 2, 3], [])),
+ [1, 2, 3],
+ )
+ self.assertEqual(
+ list(mi.interleave_randomly([], [], [1, 2, 3])),
+ [1, 2, 3],
+ )
+
+ def test_all_empty(self):
+ iterables = [], [], []
+ self.assertEqual(list(mi.interleave_randomly(*iterables)), [])
+
+ def test_no_args(self):
+ self.assertEqual(list(mi.interleave_randomly()), [])
+
+ def test_bad_type(self):
+ # Should raise TypeError if not all arguments are iterable
+ with self.assertRaises(TypeError):
+ list(mi.interleave_randomly(1, [2, 3], 'abc'))
+
+
class TestCollapse(TestCase):
"""Tests for ``collapse()``"""
@@ -3161,6 +3295,30 @@ class StripFunctionTests(TestCase):
self.assertEqual(list(mi.strip(iterable, pred)), iterable[3:-3])
+class IteratorWithWeakReferences:
+ class _AnObj:
+ pass
+
+ @classmethod
+ def FROM_SIZE(cls, size: int) -> IteratorWithWeakReferences:
+ return cls([IteratorWithWeakReferences._AnObj() for _ in range(size)])
+
+ def __init__(self, iterable: Iterable):
+ self._data = deque(element for element in iterable)
+ self._weakReferences = [weakref.ref(a) for a in self._data]
+
+ def __iter__(self) -> Iterator:
+ return self
+
+ def __next__(self) -> object:
+ if len(self._data) == 0:
+ raise StopIteration
+ return self._data.popleft()
+
+ def weakReferencesState(self) -> list[bool]:
+ return [wr() is not None for wr in self._weakReferences]
+
+
class IsliceExtendedTests(TestCase):
def test_all(self):
iterable = ['0', '1', '2', '3', '4', '5']
@@ -3202,6 +3360,134 @@ class IsliceExtendedTests(TestCase):
with self.assertRaises(TypeError):
mi.islice_extended(count())[13]
+ def test_elements_lifecycle(self):
+ # CPython does reference counting.
+ # GC is not required when ref counting is supported.
+ refCountSupported = platform.python_implementation() == 'CPython'
+
+ class TestCase(NamedTuple):
+ initialSize: int
+ slice: int
+ # list of expected intermediate elements states (alive or not)
+ # during a complete iteration
+ expectedAliveStates: list[list[int]]
+
+ # fmt: off
+ testCases = [
+ # testcases for: start>0, stop>0, step>0
+ TestCase(initialSize=3, slice=(None, None, 1), expectedAliveStates=[ # noqa: E501
+ [1, 1, 1], [0, 1, 1], [0, 0, 1], [0, 0, 0], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(0, None, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 1, 1], [0, 0, 1], [0, 0, 0], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(1, 2, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 0, 1], [0, 0, 1]]),
+ TestCase(initialSize=4, slice=(0, None, 2), expectedAliveStates=[
+ [1, 1, 1, 1], [0, 1, 1, 1], [0, 0, 0, 1], [0, 0, 0, 0]]),
+ TestCase(initialSize=5, slice=(1, 4, 2), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1]]), # noqa: E501
+ TestCase(initialSize=5, slice=(4, 1, 1), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 0, 0, 1]]),
+
+ # FYI: to process a negative start/stop index, we need to iterate
+ # on the whole iterator. All the elements will be consumed
+ # and will ALWAYS be released on full iteration completion.
+
+ # testcases for: start<0, stop>0, step>0
+ TestCase(initialSize=3, slice=(-3, None, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 1, 1], [0, 0, 1], [0, 0, 0], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(-2, 2, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 0, 1], [0, 0, 0]]),
+ TestCase(initialSize=4, slice=(-4, None, 2), expectedAliveStates=[
+ [1, 1, 1, 1], [0, 1, 1, 1], [0, 0, 0, 1], [0, 0, 0, 0]]),
+ TestCase(initialSize=5, slice=(-4, 4, 2), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=3, slice=(-2, 0, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 0, 0]]),
+
+ # testcases for: start>0, stop<0, step>0
+ TestCase(initialSize=3, slice=(None, -1, 1), expectedAliveStates=[
+ [1, 1, 1], [0, 1, 1], [0, 0, 1], [0, 0, 0]]),
+ TestCase(initialSize=4, slice=(1, -1, 1), expectedAliveStates=[
+ [1, 1, 1, 1], [0, 0, 1, 1], [0, 0, 0, 1], [0, 0, 0, 0]]),
+ TestCase(initialSize=5, slice=(None, -2, 2), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 1, 1, 1, 1], [0, 0, 0, 1, 1], [0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=5, slice=(1, -1, 2), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=5, slice=(4, -5, 2), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]]),
+
+ # testcases for: start>0, stop>0, step<0
+ TestCase(initialSize=3, slice=(None, None, -1), expectedAliveStates=[ # noqa: E501
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(2, None, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(None, 0, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [0, 1, 1], [0, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=6, slice=(3, 1, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1, 1, 1, 1], [0, 0, 1, 1, 1, 1], [0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 1, 1]]), # noqa: E501
+ TestCase(initialSize=5, slice=(1, 3, -1), expectedAliveStates=[
+ # ⚠️could be improved. Final state could be [0, 0, 1, 1, 1]
+ [1, 1, 1, 1, 1], [0, 0, 0, 0, 1]]),
+
+ # testcases for: start<0, stop>0, step<0
+ TestCase(initialSize=3, slice=(-1, None, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(-1, 0, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [0, 1, 1], [0, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=6, slice=(-2, None, -2), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=6, slice=(-2, 1, -2), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1, 1, 1, 1], [0, 0, 1, 1, 1, 1], [0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=6, slice=(-4, 4, -2), expectedAliveStates=[
+ [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]]),
+
+ # testcases for: start>0, stop<0, step<0
+ TestCase(initialSize=3, slice=(None, -3, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [0, 1, 1], [0, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=3, slice=(None, -4, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 0]]),
+ TestCase(initialSize=5, slice=(3, -4, -1), expectedAliveStates=[
+ # ⚠️could be improved, elements are only released on final step
+ [1, 1, 1, 1, 1], [0, 0, 1, 1, 1], [0, 0, 1, 1, 1], [0, 0, 0, 0, 0]]), # noqa: E501
+ TestCase(initialSize=5, slice=(1, -1, -1), expectedAliveStates=[
+ [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]]),
+ ]
+ # fmt: on
+
+ for index, testCase in enumerate(testCases):
+ with self.subTest(f"{index:02d}", testCase=testCase):
+ iterator = IteratorWithWeakReferences.FROM_SIZE(
+ testCase.initialSize
+ )
+ islice_iterator = mi.islice_extended(iterator, *testCase.slice)
+
+ aliveStates = []
+ refCountSupported or gc.collect()
+ # initial alive states
+ aliveStates.append(iterator.weakReferencesState())
+ while True:
+ try:
+ next(islice_iterator)
+ refCountSupported or gc.collect()
+ # intermediate alive states
+ aliveStates.append(iterator.weakReferencesState())
+ except StopIteration:
+ refCountSupported or gc.collect()
+ # final alive states
+ aliveStates.append(iterator.weakReferencesState())
+ break
+ self.assertEqual(aliveStates, testCase.expectedAliveStates)
+
class ConsecutiveGroupsTest(TestCase):
def test_numbers(self):
@@ -3939,6 +4225,12 @@ class SetPartitionsTests(TestCase):
self._normalize_partitions(actual),
)
+ def test_min_max(self):
+ it = 'abcdefg'
+ self.assertEqual(
+ list(mi.set_partitions(it, min_size=4, max_size=3)), []
+ )
+
class TimeLimitedTests(TestCase):
def test_basic(self):
@@ -4186,6 +4478,32 @@ class MapIfTests(TestCase):
class SampleTests(TestCase):
+ def test_specific_sample(self):
+ """Verify reproducibility."""
+
+ # Note, this test is surprisingly robust. Although it depends on the quality
+ # of the underlying libmath implementations for log, exp, and log1p, the
+ # number of samples and population size are small enough that small errors
+ # in those underlying functions won't affect the sample.
+
+ seed(8675309)
+ self.assertEqual(
+ list(mi.sample(range(10**5), k=5)),
+ [16845, 79805, 76057, 58302, 40472],
+ )
+
+ seed(8675309)
+ self.assertEqual(
+ list(mi.sample(range(10**5), counts=[1, 2] * (10**5 // 2), k=5)),
+ [87899, 53203, 38868, 11230, 50705],
+ )
+
+ seed(8675309)
+ self.assertEqual(
+ list(mi.sample(range(10**5), weights=range(1, 10**5 + 1), k=5)),
+ [50915, 33816, 32250, 98284, 43517],
+ )
+
def test_unit_case(self):
"""Test against a fixed case by seeding the random module."""
# Beware that this test really just verifies random.random() behavior.
@@ -5969,3 +6287,134 @@ class DoubleStarMapTests(TestCase):
actual = list(mi.doublestarmap(lambda x: x, []))
expected = []
self.assertEqual(actual, expected)
+
+
+class ArgMinArgMaxTests(TestCase):
+ def test_basic(self):
+ for i, iterable, expected_min, expected_max in (
+ (1, [10, 2, 20, 5, 17, 4], 1, 2),
+ (2, [10, -2, -20, 5, 17, 4], 2, 4),
+ (3, [10, 10, 20, 10], 0, 2),
+ (4, [30, 30, 20, 30], 2, 0),
+ ):
+ with self.subTest(i=i):
+ self.assertEqual(mi.argmin(iterable), expected_min)
+ self.assertEqual(mi.argmax(iterable), expected_max)
+
+ def test_key(self):
+ for i, iterable, key, expected_min, expected_max in (
+ (1, [10, -2, -20, 5, 17, 4], abs, 1, 2),
+ (
+ 2,
+ [[0] * 10, [0] * 5, [0] * 3, [0] * 12, [0] * 2, [0] * 3],
+ len,
+ 4,
+ 3,
+ ),
+ ):
+ with self.subTest(i=i):
+ self.assertEqual(mi.argmin(iterable, key=key), expected_min)
+ self.assertEqual(mi.argmax(iterable, key=key), expected_max)
+
+
+class ExtractTests(TestCase):
+ def test_basics(self):
+ extract = mi.extract
+ data = 'abcdefghijklmnopqrstuvwxyz'
+
+ # Test iterator inputs, increasing and decreasing indices, and repeats.
+ self.assertEqual(
+ list(extract(iter(data), iter([7, 4, 11, 11, 14]))),
+ ['h', 'e', 'l', 'l', 'o'],
+ )
+
+ # Empty indices
+ self.assertEqual(list(extract(iter(data), iter([]))), [])
+
+ # Result is an iterator
+ iterator = extract('abc', [0, 1, 2])
+ self.assertTrue(hasattr(iterator, '__next__'))
+
+ # Error cases
+
+ with self.assertRaises(TypeError):
+ list(extract(None, [])) # Non-iterable data source
+ with self.assertRaises(TypeError):
+ list(extract(data, None)) # Non-iterable indices
+ with self.assertRaises(ValueError):
+ list(extract(data, [0.0, 1.0, 2.0])) # Non-integer indices
+ with self.assertRaises(ValueError):
+ list(extract(data, [1, 2, -3])) # Negative indices
+ with self.assertRaises(IndexError):
+ list(extract(data, [1, 2, len(data)])) # Indices out of range
+
+ def test_negative_one_bug(self):
+ # When the lowest index was exactly -1, it matched the initial
+ # iterator_position of -1 giving a zero advance step.
+ extract = mi.extract
+
+ with self.assertRaises(ValueError):
+ list(extract('abcdefg', [1, 2, -1]))
+
+ def test_none_value_bug(self):
+ # The buffer used to be a list with unused slots marked with None.
+ # The mark got conflated with None values in the data stream.
+ extract = mi.extract
+ data = ['a', 'b', 'None', 'c', 'd']
+ self.assertEqual(list(extract(data, range(5))), data)
+
+ def test_all_orderings(self):
+ # Thorough test for all cases of five indices to detect
+ # obscure corner case bugs.
+ extract = mi.extract
+
+ data = 'abcdefg'
+ for indices in product(range(6), repeat=5):
+ with self.subTest(indices=indices):
+ actual = tuple(extract(data, indices))
+ expected = itemgetter(*indices)(data)
+ self.assertEqual(actual, expected)
+
+ def test_early_free(self):
+ # No references are held for skipped values or for previously
+ # emitted values regardless of how long they were in the buffer.
+
+ extract = mi.extract
+
+ class TrackDels(str):
+ def __del__(self):
+ dead.add(str(self))
+
+ dead = set()
+ iterator = extract(map(TrackDels, 'ABCDEF'), [3, 2, 4, 5])
+
+ value = next(iterator)
+ gc.collect() # Force collection on PyPy.
+ self.assertEqual(value, 'D') # Returns D. Buffered C is alive.
+ self.assertEqual(dead, {'A', 'B'}) # A and B are dead.
+
+ value = next(iterator)
+ gc.collect() # Force collection on PyPy
+ self.assertEqual(value, 'C') # Returns C.
+
+ value = next(iterator)
+ gc.collect() # Force collection on PyPy
+ self.assertEqual(value, 'E') # Returns E.
+ self.assertEqual(dead, {'A', 'B', 'D', 'C'}) # D and C are now dead.
+
+ def test_lazy_consumption(self):
+ extract = mi.extract
+
+ input_stream = mi.peekable(iter('ABCDEFGHIJKLM'))
+ iterator = extract(input_stream, [4, 2, 10])
+
+ self.assertEqual(next(iterator), 'E') # C is still buffered
+ self.assertEqual(input_stream.peek(), 'F')
+
+ self.assertEqual(next(iterator), 'C')
+ self.assertEqual(input_stream.peek(), 'F')
+
+ # Infinite input
+ self.assertEqual(
+ list(extract(count(), [5, 7, 3, 9, 4])), [5, 7, 3, 9, 4]
+ )
diff --git a/contrib/python/more-itertools/py3/tests/test_recipes.py b/contrib/python/more-itertools/py3/tests/test_recipes.py
index 63be39426eb..2ee5db6f855 100644
--- a/contrib/python/more-itertools/py3/tests/test_recipes.py
+++ b/contrib/python/more-itertools/py3/tests/test_recipes.py
@@ -3,14 +3,17 @@ from decimal import Decimal
from doctest import DocTestSuite
from fractions import Fraction
from functools import reduce
-from itertools import combinations, count, groupby, permutations
+from itertools import combinations, count, groupby, permutations, islice
from operator import mul
from math import comb, prod, factorial
+from statistics import mean
from sys import version_info
from unittest import TestCase, skipIf
from unittest.mock import patch
import more_itertools as mi
+import statistics
+import random
def load_tests(loader, tests, ignore):
@@ -1124,6 +1127,59 @@ class ReshapeTests(TestCase):
actual = list(mi.reshape(matrix, cols))
self.assertEqual(actual, expected)
+ def test_multidimensional(self):
+ reshape = mi.reshape
+
+ def shape(tensor):
+ if not hasattr(tensor, '__iter__'):
+ return ()
+ seq = list(tensor)
+ return (len(seq),) + shape(seq[0])
+
+ matrix = [(0, 1), (2, 3), (4, 5)]
+ self.assertEqual(shape(matrix), (3, 2))
+
+ for new_shape in [
+ (2, 3),
+ (6,),
+ (6, 1),
+ (1, 6),
+ (2, 1, 3, 1),
+ (1, 1, 3, 1, 2),
+ ]:
+ with self.subTest(new_shape=new_shape):
+ new_matrix = reshape(matrix, new_shape)
+ self.assertEqual(shape(new_matrix), new_shape)
+
+ # Truncation: Input larger than the requested shape
+ self.assertEqual(list(reshape(matrix, [3])), [0, 1, 2])
+
+ # Incomplete structure: Input smaller than the requested shape
+ self.assertEqual(list(reshape(matrix, [8])), [0, 1, 2, 3, 4, 5])
+
+ # Str and bytes treated as scalars
+ word_matrix = [[['ab', b'de', 'gh', b'jk']]] # Shape: 1 x 1 x 4
+ self.assertEqual(
+ list(reshape(word_matrix, (2, 2))),
+ [('ab', b'de'), ('gh', b'jk')],
+ )
+
+ # Empty input
+ self.assertEqual(list(reshape([[]], shape=(1,))), [])
+
+ # Non-uniform input: scalar where a tensor is expected
+ with self.assertRaises(TypeError):
+ list(mi.reshape([[10, 20, 30], 40], shape=(4,)))
+
+ # Non-integer indices
+ with self.assertRaises((TypeError, ValueError)):
+ matrix = [(0, 1), (2, 3), (4, 5)]
+ list(reshape(matrix, ('a', 'b', 'c')))
+
+ # Indices smaller than one
+ with self.assertRaises(ValueError):
+ list(reshape(matrix, (6, 0, 1)))
+
class MatMulTests(TestCase):
def test_n_by_n(self):
@@ -1438,3 +1494,79 @@ class MultinomialTests(TestCase):
multinomial(5, 'x') # No non-numeric inputs
with self.assertRaises(TypeError):
multinomial([5, 7]) # No sequence inputs
+
+
+class RunningMedianTests(TestCase):
+ def test_vs_statistics_median(self):
+ running_median = mi.running_median
+
+ for data in [
+ random.choices(range(-500, 500), k=500),
+ # Apply unary plus to force context rounding.
+ [+Decimal(random.uniform(-500, 500)) for _ in range(500)],
+ [
+ Fraction(random.randrange(-500, 500), random.randrange(1, 500))
+ for _ in range(500)
+ ],
+ ]:
+ with self.subTest(data=data):
+ for k, rm in enumerate(running_median(iter(data)), start=1):
+ expected = statistics.median(data[:k])
+ self.assertEqual(rm, expected)
+ self.assertEqual(type(rm), type(expected))
+
+ self.assertEqual(list(running_median([])), []) # Empty input
+
+ def test_vs_statistics_median_windowed(self):
+ running_median = mi.running_median
+ size = 10
+
+ for data in [
+ random.choices(range(-500, 500), k=500),
+ # Apply unary plus to force context rounding.
+ [+Decimal(random.uniform(-500, 500)) for _ in range(500)],
+ [
+ Fraction(random.randrange(-500, 500), random.randrange(1, 500))
+ for _ in range(500)
+ ],
+ ]:
+ with self.subTest(data=data):
+ iterator = running_median(iter(data), maxlen=size)
+ for k, rm in enumerate(iterator, start=1):
+ expected = statistics.median(data[max(0, k - size) : k])
+ self.assertEqual(rm, expected)
+ self.assertEqual(type(rm), type(expected))
+
+ self.assertEqual(list(running_median([], maxlen=1)), []) # Empty input
+
+ # Window size of 1 should return the original dataset unchanged
+ data = random.choices(range(-500, 500), k=500)
+ self.assertEqual(list(running_median(data, maxlen=1)), data)
+
+ # Window size of 2 is a moving average of pairs
+ data = random.choices(range(-500, 500), k=500)
+ expected = list(map(mean, mi.pairwise(data)))
+ actual = list(islice(running_median(data, maxlen=2), 1, None))
+ self.assertEqual(actual, expected)
+
+ # A window larger than the dataset should give the same
+ # result as an unbounded running median.
+ data = random.choices(range(-500, 500), k=500)
+ self.assertEqual(
+ list(running_median(data, maxlen=600)), list(running_median(data))
+ )
+
+ def test_error_cases(self):
+ running_median = mi.running_median
+ with self.assertRaises(TypeError):
+ running_median(1234) # Non-iterable input
+ with self.assertRaises(TypeError):
+ running_median([], maxlen=3.0) # Non-integer type for window size
+ with self.assertRaises(ValueError):
+ running_median([], maxlen=0) # Invalid window size
+ with self.assertRaises(TypeError):
+ list(running_median([3 + 4j, 5 - 7j])) # Unorderable input type
+ with self.assertRaises(TypeError):
+ list(
+ running_median(['abc', 'def', 'ghi'])
+ ) # Input type that doesn't support division
diff --git a/contrib/python/more-itertools/py3/ya.make b/contrib/python/more-itertools/py3/ya.make
index d8f6243c812..3a87b45d9bd 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.7.0)
+VERSION(10.8.0)
LICENSE(MIT)