diff options
author | robot-piglet <[email protected]> | 2025-09-17 23:38:38 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-09-17 23:51:08 +0300 |
commit | 0aadd2b8b6f8a556cd5e17ed31adfaae1b4b456c (patch) | |
tree | c14edb738750e4eb120c35fc2eace6a8afce3937 /contrib/python | |
parent | 28b8afacb9cf086be40ad51d270a6aa09a3dc2d4 (diff) |
Intermediate changes
commit_hash:46fb8895a86e7e705f3f141ab4e54f33d325f394
Diffstat (limited to 'contrib/python')
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) |