Browse Source

Intermediate changes
commit_hash:8c2a03558616560deb74e3412a33769b4f519a0a

robot-piglet 1 month ago
parent
commit
f12d0f8789

+ 7 - 3
contrib/python/more-itertools/py3/.dist-info/METADATA

@@ -1,25 +1,26 @@
 Metadata-Version: 2.1
 Name: more-itertools
-Version: 10.5.0
+Version: 10.6.0
 Summary: More routines for operating on iterables, beyond itertools
 Keywords: itertools,iterator,iteration,filter,peek,peekable,chunk,chunked
 Author-email: Erik Rose <erikrose@grinchcentral.com>
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/x-rst
 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.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Libraries
+Project-URL: Documentation, https://more-itertools.readthedocs.io/en/stable/
 Project-URL: Homepage, https://github.com/more-itertools/more-itertools
 
 ==============
@@ -142,6 +143,8 @@ Python iterables.
 |                        | `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>`_,                                                                             |
+|                        | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_,                                                                           |
 |                        | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_,                                                                                 |
 |                        | `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>`_,                                                   |
@@ -185,6 +188,7 @@ Python iterables.
 |                        | `numeric_range <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.numeric_range>`_,                                                                   |
 |                        | `side_effect <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.side_effect>`_,                                                                       |
 |                        | `iterate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iterate>`_,                                                                               |
+|                        | `loops <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.loops>`_,                                                                                   |
 |                        | `difference <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.difference>`_,                                                                         |
 |                        | `make_decorator <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.make_decorator>`_,                                                                 |
 |                        | `SequenceView <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.SequenceView>`_,                                                                     |

+ 3 - 0
contrib/python/more-itertools/py3/README.rst

@@ -118,6 +118,8 @@ Python iterables.
 |                        | `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>`_,                                                                             |
+|                        | `nth_prime <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.nth_prime>`_,                                                                           |
 |                        | `matmul <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.matmul>`_,                                                                                 |
 |                        | `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>`_,                                                   |
@@ -161,6 +163,7 @@ Python iterables.
 |                        | `numeric_range <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.numeric_range>`_,                                                                   |
 |                        | `side_effect <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.side_effect>`_,                                                                       |
 |                        | `iterate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iterate>`_,                                                                               |
+|                        | `loops <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.loops>`_,                                                                                   |
 |                        | `difference <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.difference>`_,                                                                         |
 |                        | `make_decorator <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.make_decorator>`_,                                                                 |
 |                        | `SequenceView <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.SequenceView>`_,                                                                     |

+ 1 - 1
contrib/python/more-itertools/py3/more_itertools/__init__.py

@@ -3,4 +3,4 @@
 from .more import *  # noqa
 from .recipes import *  # noqa
 
-__version__ = '10.5.0'
+__version__ = '10.6.0'

+ 63 - 45
contrib/python/more-itertools/py3/more_itertools/more.py

@@ -25,7 +25,7 @@ from itertools import (
 from math import comb, e, exp, factorial, floor, fsum, log, log1p, perm, tau
 from queue import Empty, Queue
 from random import random, randrange, shuffle, uniform
-from operator import itemgetter, mul, sub, gt, lt, le
+from operator import itemgetter, mul, sub, gt, lt
 from sys import hexversion, maxsize
 from time import monotonic
 
@@ -35,7 +35,9 @@ from .recipes import (
     UnequalIterablesError,
     consume,
     flatten,
+    nth,
     powerset,
+    sieve,
     take,
     unique_everseen,
     all_equal,
@@ -104,6 +106,7 @@ __all__ = [
     'minmax',
     'nth_or_last',
     'nth_permutation',
+    'nth_prime',
     'nth_product',
     'nth_combination_with_replacement',
     'numeric_range',
@@ -215,8 +218,8 @@ def first(iterable, default=_marker):
         return item
     if default is _marker:
         raise ValueError(
-            'first() was called on an empty iterable, and no '
-            'default value was provided.'
+            'first() was called on an empty iterable, '
+            'and no default value was provided.'
         )
     return default
 
@@ -237,15 +240,14 @@ def last(iterable, default=_marker):
         if isinstance(iterable, Sequence):
             return iterable[-1]
         # Work around https://bugs.python.org/issue38525
-        elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0):
+        if hasattr(iterable, '__reversed__'):
             return next(reversed(iterable))
-        else:
-            return deque(iterable, maxlen=1)[-1]
+        return deque(iterable, maxlen=1)[-1]
     except (IndexError, TypeError, StopIteration):
         if default is _marker:
             raise ValueError(
-                'last() was called on an empty iterable, and no default was '
-                'provided.'
+                'last() was called on an empty iterable, '
+                'and no default value was provided.'
             )
         return default
 
@@ -569,8 +571,8 @@ def one(iterable, too_short=None, too_long=None):
         pass
     else:
         msg = (
-            'Expected exactly one item in iterable, but got {!r}, {!r}, '
-            'and perhaps more.'.format(first_value, second_value)
+            f'Expected exactly one item in iterable, but got {first_value!r}, '
+            f'{second_value!r}, and perhaps more.'
         )
         raise too_long or ValueError(msg)
 
@@ -631,13 +633,13 @@ def strictly_n(iterable, n, too_short=None, too_long=None):
     if too_short is None:
         too_short = lambda item_count: raise_(
             ValueError,
-            'Too few items in iterable (got {})'.format(item_count),
+            f'Too few items in iterable (got {item_count})',
         )
 
     if too_long is None:
         too_long = lambda item_count: raise_(
             ValueError,
-            'Too many items in iterable (got at least {})'.format(item_count),
+            f'Too many items in iterable (got at least {item_count})',
         )
 
     it = iter(iterable)
@@ -1118,10 +1120,8 @@ def spy(iterable, n=1):
         [1, 2, 3, 4, 5]
 
     """
-    it = iter(iterable)
-    head = take(n, it)
-
-    return head.copy(), chain(head, it)
+    p, q = tee(iterable)
+    return take(n, q), p
 
 
 def interleave(*iterables):
@@ -1558,8 +1558,8 @@ def split_into(iterable, sizes):
         [[1], [2, 3], [4], []]
 
     When a ``None`` object is encountered in *sizes*, the returned list will
-    contain items up to the end of *iterable* the same way that itertools.slice
-    does:
+    contain items up to the end of *iterable* the same way that
+    :func:`itertools.slice` does:
 
         >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None]))
         [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]]
@@ -2167,13 +2167,11 @@ class numeric_range(abc.Sequence, abc.Hashable):
             self._start, self._stop, self._step = args
         elif argc == 0:
             raise TypeError(
-                'numeric_range expected at least '
-                '1 argument, got {}'.format(argc)
+                f'numeric_range expected at least 1 argument, got {argc}'
             )
         else:
             raise TypeError(
-                'numeric_range expected at most '
-                '3 arguments, got {}'.format(argc)
+                f'numeric_range expected at most 3 arguments, got {argc}'
             )
 
         self._zero = type(self._step)(0)
@@ -2236,7 +2234,7 @@ class numeric_range(abc.Sequence, abc.Hashable):
         else:
             raise TypeError(
                 'numeric range indices must be '
-                'integers or slices, not {}'.format(type(key).__name__)
+                f'integers or slices, not {type(key).__name__}'
             )
 
     def __hash__(self):
@@ -2277,13 +2275,10 @@ class numeric_range(abc.Sequence, abc.Hashable):
 
     def __repr__(self):
         if self._step == 1:
-            return "numeric_range({}, {})".format(
-                repr(self._start), repr(self._stop)
-            )
-        else:
-            return "numeric_range({}, {}, {})".format(
-                repr(self._start), repr(self._stop), repr(self._step)
-            )
+            return f"numeric_range({self._start!r}, {self._stop!r})"
+        return (
+            f"numeric_range({self._start!r}, {self._stop!r}, {self._step!r})"
+        )
 
     def __reversed__(self):
         return iter(
@@ -2307,7 +2302,7 @@ class numeric_range(abc.Sequence, abc.Hashable):
                 if r == self._zero:
                     return int(q)
 
-        raise ValueError("{} is not in numeric range".format(value))
+        raise ValueError(f"{value} is not in numeric range")
 
     def _get_by_index(self, i):
         if i < 0:
@@ -2781,7 +2776,7 @@ class SequenceView(Sequence):
         return len(self._target)
 
     def __repr__(self):
-        return '{}({})'.format(self.__class__.__name__, repr(self._target))
+        return f'{self.__class__.__name__}({self._target!r})'
 
 
 class seekable:
@@ -3443,8 +3438,8 @@ def only(iterable, default=None, too_long=None):
         pass
     else:
         msg = (
-            'Expected exactly one item in iterable, but got {!r}, {!r}, '
-            'and perhaps more.'.format(first_value, second_value)
+            f'Expected exactly one item in iterable, but got {first_value!r}, '
+            f'{second_value!r}, and perhaps more.'
         )
         raise too_long or ValueError(msg)
 
@@ -3726,9 +3721,11 @@ def _sample_counted(population, k, counts, strict):
         reservoir = []
         for _ in range(k):
             reservoir.append(feed(0))
-        if strict and len(reservoir) < k:
-            raise ValueError('Sample larger than population')
 
+    if strict and len(reservoir) < k:
+        raise ValueError('Sample larger than population')
+
+    with suppress(StopIteration):
         W = 1.0
         while True:
             W *= exp(log(random()) / k)
@@ -3821,15 +3818,16 @@ def is_sorted(iterable, key=None, reverse=False, strict=False):
 
     The function returns ``False`` after encountering the first out-of-order
     item, which means it may produce results that differ from the built-in
-    :func:`sorted` function for objects with unusual comparison dynamics.
-    If there are no out-of-order items, the iterable is exhausted.
+    :func:`sorted` function for objects with unusual comparison dynamics
+    (like ``math.nan``). If there are no out-of-order items, the iterable is
+    exhausted.
     """
-    compare = le if strict else lt
     it = iterable if (key is None) else map(key, iterable)
-    it_1, it_2 = tee(it)
-    next(it_2 if reverse else it_1, None)
-
-    return not any(map(compare, it_1, it_2))
+    a, b = tee(it)
+    next(b, None)
+    if reverse:
+        b, a = a, b
+    return all(map(lt, a, b)) if strict else not any(map(lt, b, a))
 
 
 class AbortThread(BaseException):
@@ -4822,8 +4820,8 @@ def outer_product(func, xs, ys, *args, **kwargs):
 
     >>> xs = ['A', 'B', 'A', 'A', 'B', 'B', 'A', 'A', 'B', 'B']
     >>> ys = ['X', 'X', 'X', 'Y', 'Z', 'Z', 'Y', 'Y', 'Z', 'Z']
-    >>> rows = list(zip(xs, ys))
-    >>> count_rows = lambda x, y: rows.count((x, y))
+    >>> pair_counts = Counter(zip(xs, ys))
+    >>> count_rows = lambda x, y: pair_counts[x, y]
     >>> list(outer_product(count_rows, sorted(set(xs)), sorted(set(ys))))
     [(2, 3, 0), (1, 0, 4)]
 
@@ -4978,3 +4976,23 @@ def doublestarmap(func, iterable):
     """
     for item in iterable:
         yield func(**item)
+
+
+def _nth_prime_ub(n):
+    "Upper bound for the nth prime (counting from 1)."
+    # https://en.wikipedia.org/wiki/Prime-counting_function#Inequalities
+    return n * log(n * log(n)) if n >= 6 else 11.1
+
+
+def nth_prime(n):
+    """Return the nth prime (counting from 0).
+
+    >>> nth_prime(0)
+    2
+    >>> nth_prime(100)
+    547
+    """
+    if n < 0:
+        raise ValueError
+    limit = math.ceil(_nth_prime_ub(n + 1))
+    return nth(sieve(limit), n)

+ 132 - 16
contrib/python/more-itertools/py3/more_itertools/more.pyi

@@ -5,27 +5,141 @@ from __future__ import annotations
 import sys
 import types
 
-from typing import (
-    Any,
-    Callable,
+from collections.abc import (
     Container,
-    ContextManager,
-    Generic,
     Hashable,
-    Mapping,
     Iterable,
     Iterator,
     Mapping,
-    overload,
     Reversible,
     Sequence,
     Sized,
-    Type,
+)
+from contextlib import AbstractContextManager
+from typing import (
+    Any,
+    Callable,
+    Generic,
     TypeVar,
+    overload,
     type_check_only,
 )
 from typing_extensions import Protocol
 
+__all__ = [
+    'AbortThread',
+    'SequenceView',
+    'UnequalIterablesError',
+    'adjacent',
+    'all_unique',
+    'always_iterable',
+    'always_reversible',
+    'bucket',
+    'callback_iter',
+    'chunked',
+    'chunked_even',
+    'circular_shifts',
+    'collapse',
+    'combination_index',
+    'combination_with_replacement_index',
+    'consecutive_groups',
+    'constrained_batches',
+    'consumer',
+    'count_cycle',
+    'countable',
+    'dft',
+    'difference',
+    'distinct_combinations',
+    'distinct_permutations',
+    'distribute',
+    'divide',
+    'doublestarmap',
+    'duplicates_everseen',
+    'duplicates_justseen',
+    'classify_unique',
+    'exactly_n',
+    'filter_except',
+    'filter_map',
+    'first',
+    'gray_product',
+    'groupby_transform',
+    'ichunked',
+    'iequals',
+    'idft',
+    'ilen',
+    'interleave',
+    'interleave_evenly',
+    'interleave_longest',
+    'intersperse',
+    'is_sorted',
+    'islice_extended',
+    'iterate',
+    'iter_suppress',
+    'join_mappings',
+    'last',
+    'locate',
+    'longest_common_prefix',
+    'lstrip',
+    'make_decorator',
+    'map_except',
+    'map_if',
+    'map_reduce',
+    'mark_ends',
+    'minmax',
+    'nth_or_last',
+    'nth_permutation',
+    'nth_prime',
+    'nth_product',
+    'nth_combination_with_replacement',
+    'numeric_range',
+    'one',
+    'only',
+    'outer_product',
+    'padded',
+    'partial_product',
+    'partitions',
+    'peekable',
+    'permutation_index',
+    'powerset_of_sets',
+    'product_index',
+    'raise_',
+    'repeat_each',
+    'repeat_last',
+    'replace',
+    'rlocate',
+    'rstrip',
+    'run_length',
+    'sample',
+    'seekable',
+    'set_partitions',
+    'side_effect',
+    'sliced',
+    'sort_together',
+    'split_after',
+    'split_at',
+    'split_before',
+    'split_into',
+    'split_when',
+    'spy',
+    'stagger',
+    'strip',
+    'strictly_n',
+    'substrings',
+    'substrings_indexes',
+    'takewhile_inclusive',
+    'time_limited',
+    'unique_in_window',
+    'unique_to_each',
+    'unzip',
+    'value_chain',
+    'windowed',
+    'windowed_complete',
+    'with_iter',
+    'zip_broadcast',
+    'zip_equal',
+    'zip_offset',
+]
+
 # Type and type variable definitions
 _T = TypeVar('_T')
 _T1 = TypeVar('_T1')
@@ -38,7 +152,7 @@ _V = TypeVar('_V')
 _W = TypeVar('_W')
 _T_co = TypeVar('_T_co', covariant=True)
 _GenFn = TypeVar('_GenFn', bound=Callable[..., Iterator[Any]])
-_Raisable = BaseException | Type[BaseException]
+_Raisable = BaseException | type[BaseException]
 
 # The type of isinstance's second argument (from typeshed builtins)
 if sys.version_info >= (3, 10):
@@ -91,7 +205,7 @@ def consumer(func: _GenFn) -> _GenFn: ...
 def ilen(iterable: Iterable[_T]) -> int: ...
 def iterate(func: Callable[[_T], _T], start: _T) -> Iterator[_T]: ...
 def with_iter(
-    context_manager: ContextManager[Iterable[_T]],
+    context_manager: AbstractContextManager[Iterable[_T]],
 ) -> Iterator[_T]: ...
 def one(
     iterable: Iterable[_T],
@@ -410,7 +524,7 @@ class numeric_range(Generic[_T, _U], Sequence[_T], Hashable, Reversible[_T]):
     def __len__(self) -> int: ...
     def __reduce__(
         self,
-    ) -> tuple[Type[numeric_range[_T, _U]], tuple[_T, _T, _U]]: ...
+    ) -> tuple[type[numeric_range[_T, _U]], tuple[_T, _T, _U]]: ...
     def __repr__(self) -> str: ...
     def __reversed__(self) -> Iterator[_T]: ...
     def count(self, value: _T) -> int: ...
@@ -567,12 +681,12 @@ def distinct_combinations(
 def filter_except(
     validator: Callable[[Any], object],
     iterable: Iterable[_T],
-    *exceptions: Type[BaseException],
+    *exceptions: type[BaseException],
 ) -> Iterator[_T]: ...
 def map_except(
     function: Callable[[Any], _U],
     iterable: Iterable[_T],
-    *exceptions: Type[BaseException],
+    *exceptions: type[BaseException],
 ) -> Iterator[_U]: ...
 def map_if(
     iterable: Iterable[Any],
@@ -587,7 +701,7 @@ def _sample_counted(
     population: Iterator[_T], k: int, counts: Iterable[int], strict: bool
 ) -> list[_T]: ...
 def _sample_weighted(
-    iterator: Iterator[_T], k: int, weights, strict
+    iterator: Iterator[_T], k: int, weights: Iterator[float], strict: bool
 ) -> list[_T]: ...
 def sample(
     iterable: Iterable[_T],
@@ -617,7 +731,7 @@ class callback_iter(Generic[_T], Iterator[_T]):
     def __enter__(self) -> callback_iter[_T]: ...
     def __exit__(
         self,
-        exc_type: Type[BaseException] | None,
+        exc_type: type[BaseException] | None,
         exc_value: BaseException | None,
         traceback: types.TracebackType | None,
     ) -> bool | None: ...
@@ -797,7 +911,7 @@ def outer_product(
 ) -> Iterator[tuple[_V, ...]]: ...
 def iter_suppress(
     iterable: Iterable[_T],
-    *exceptions: Type[BaseException],
+    *exceptions: type[BaseException],
 ) -> Iterator[_T]: ...
 def filter_map(
     func: Callable[[_T], _V | None],
@@ -813,3 +927,5 @@ def doublestarmap(
 ) -> Iterator[_T]: ...
 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: ...

+ 132 - 8
contrib/python/more-itertools/py3/more_itertools/recipes.py

@@ -13,7 +13,7 @@ import operator
 
 from collections import deque
 from collections.abc import Sized
-from functools import partial, reduce
+from functools import lru_cache, partial
 from itertools import (
     chain,
     combinations,
@@ -42,8 +42,10 @@ __all__ = [
     'factor',
     'flatten',
     'grouper',
+    'is_prime',
     'iter_except',
     'iter_index',
+    'loops',
     'matmul',
     'ncycles',
     'nth',
@@ -872,8 +874,10 @@ def polynomial_from_roots(roots):
     >>> polynomial_from_roots(roots)  # x^3 - 4 * x^2 - 17 * x + 60
     [1, -4, -17, 60]
     """
-    factors = zip(repeat(1), map(operator.neg, roots))
-    return list(reduce(convolve, factors, [1]))
+    poly = [1]
+    for root in roots:
+        poly = list(convolve(poly, (1, -root)))
+    return poly
 
 
 def iter_index(iterable, value, start=0, stop=None):
@@ -1005,20 +1009,56 @@ def matmul(m1, m2):
     return batched(starmap(_sumprod, product(m1, transpose(m2))), n)
 
 
+def _factor_pollard(n):
+    # Return a factor of n using Pollard's rho algorithm
+    gcd = math.gcd
+    for b in range(1, n - 2):
+        x = y = 2
+        d = 1
+        while d == 1:
+            x = (x * x + b) % n
+            y = (y * y + b) % n
+            y = (y * y + b) % n
+            d = gcd(x - y, n)
+        if d != n:
+            return d
+    raise ValueError('prime or under 5')
+
+
+_primes_below_211 = tuple(sieve(211))
+
+
 def factor(n):
     """Yield the prime factors of n.
 
     >>> list(factor(360))
     [2, 2, 2, 3, 3, 5]
+
+    Finds small factors with trial division.  Larger factors are
+    either verified as prime with ``is_prime`` or split into
+    smaller factors with Pollard's rho algorithm.
     """
-    for prime in sieve(math.isqrt(n) + 1):
+
+    # Corner case reduction
+    if n < 2:
+        return
+
+    # Trial division reduction
+    for prime in _primes_below_211:
         while not n % prime:
             yield prime
             n //= prime
-            if n == 1:
-                return
-    if n > 1:
-        yield n
+
+    # Pollard's rho reduction
+    primes = []
+    todo = [n] if n > 1 else []
+    for n in todo:
+        if n < 211**2 or is_prime(n):
+            primes.append(n)
+        else:
+            fact = _factor_pollard(n)
+            todo += (fact, n // fact)
+    yield from sorted(primes)
 
 
 def polynomial_eval(coefficients, x):
@@ -1073,3 +1113,87 @@ def totient(n):
     for prime in set(factor(n)):
         n -= n // prime
     return n
+
+
+# Miller–Rabin primality test: https://oeis.org/A014233
+_perfect_tests = [
+    (2047, (2,)),
+    (9080191, (31, 73)),
+    (4759123141, (2, 7, 61)),
+    (1122004669633, (2, 13, 23, 1662803)),
+    (2152302898747, (2, 3, 5, 7, 11)),
+    (3474749660383, (2, 3, 5, 7, 11, 13)),
+    (18446744073709551616, (2, 325, 9375, 28178, 450775, 9780504, 1795265022)),
+    (
+        3317044064679887385961981,
+        (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41),
+    ),
+]
+
+
+@lru_cache
+def _shift_to_odd(n):
+    'Return s, d such that 2**s * d == n'
+    s = ((n - 1) ^ n).bit_length() - 1
+    d = n >> s
+    assert (1 << s) * d == n and d & 1 and s >= 0
+    return s, d
+
+
+def _strong_probable_prime(n, base):
+    assert (n > 2) and (n & 1) and (2 <= base < n)
+
+    s, d = _shift_to_odd(n - 1)
+
+    x = pow(base, d, n)
+    if x == 1 or x == n - 1:
+        return True
+
+    for _ in range(s - 1):
+        x = x * x % n
+        if x == n - 1:
+            return True
+
+    return False
+
+
+def is_prime(n):
+    """Return ``True`` if *n* is prime and ``False`` otherwise.
+
+    >>> is_prime(37)
+    True
+    >>> is_prime(3 * 13)
+    False
+    >>> is_prime(18_446_744_073_709_551_557)
+    True
+
+    This function uses the Miller-Rabin primality test, which can return false
+    positives for very large inputs. For values of *n* below 10**24
+    there are no false positives. For larger values, there is less than
+    a 1 in 2**128 false positive rate. Multiple tests can further reduce the
+    chance of a false positive.
+    """
+    if n < 17:
+        return n in {2, 3, 5, 7, 11, 13}
+    if not (n & 1 and n % 3 and n % 5 and n % 7 and n % 11 and n % 13):
+        return False
+    for limit, bases in _perfect_tests:
+        if n < limit:
+            break
+    else:
+        bases = [randrange(2, n - 1) for i in range(64)]
+    return all(_strong_probable_prime(n, base) for base in bases)
+
+
+def loops(n):
+    """Returns an iterable with *n* elements for efficient looping.
+    Like ``range(n)`` but doesn't create integers.
+
+    >>> i = 0
+    >>> for _ in loops(5):
+    ...     i += 1
+    >>> i
+    5
+
+    """
+    return repeat(None, n)

+ 61 - 7
contrib/python/more-itertools/py3/more_itertools/recipes.pyi

@@ -2,17 +2,65 @@
 
 from __future__ import annotations
 
+from collections.abc import Iterable, Iterator, Sequence
 from typing import (
     Any,
     Callable,
-    Iterable,
-    Iterator,
-    overload,
-    Sequence,
-    Type,
     TypeVar,
+    overload,
 )
 
+__all__ = [
+    'all_equal',
+    'batched',
+    'before_and_after',
+    'consume',
+    'convolve',
+    'dotproduct',
+    'first_true',
+    'factor',
+    'flatten',
+    'grouper',
+    'is_prime',
+    'iter_except',
+    'iter_index',
+    'loops',
+    'matmul',
+    'ncycles',
+    'nth',
+    'nth_combination',
+    'padnone',
+    'pad_none',
+    'pairwise',
+    'partition',
+    'polynomial_eval',
+    'polynomial_from_roots',
+    'polynomial_derivative',
+    'powerset',
+    'prepend',
+    'quantify',
+    'reshape',
+    'random_combination_with_replacement',
+    'random_combination',
+    'random_permutation',
+    'random_product',
+    'repeatfunc',
+    'roundrobin',
+    'sieve',
+    'sliding_window',
+    'subslices',
+    'sum_of_squares',
+    'tabulate',
+    'tail',
+    'take',
+    'totient',
+    'transpose',
+    'triplewise',
+    'unique',
+    'unique_everseen',
+    'unique_justseen',
+]
+
 # Type and type variable definitions
 _T = TypeVar('_T')
 _T1 = TypeVar('_T1')
@@ -69,13 +117,13 @@ def unique(
 @overload
 def iter_except(
     func: Callable[[], _T],
-    exception: Type[BaseException] | tuple[Type[BaseException], ...],
+    exception: type[BaseException] | tuple[type[BaseException], ...],
     first: None = ...,
 ) -> Iterator[_T]: ...
 @overload
 def iter_except(
     func: Callable[[], _T],
-    exception: Type[BaseException] | tuple[Type[BaseException], ...],
+    exception: type[BaseException] | tuple[type[BaseException], ...],
     first: Callable[[], _U],
 ) -> Iterator[_T | _U]: ...
 @overload
@@ -129,8 +177,14 @@ def reshape(
     matrix: Iterable[Iterable[_T]], cols: int
 ) -> Iterator[tuple[_T, ...]]: ...
 def matmul(m1: Sequence[_T], m2: Sequence[_T]) -> Iterator[tuple[_T]]: ...
+def _factor_trial(n: int) -> Iterator[int]: ...
+def _factor_pollard(n: int) -> int: ...
 def factor(n: int) -> Iterator[int]: ...
 def polynomial_eval(coefficients: Sequence[_T], x: _U) -> _U: ...
 def sum_of_squares(it: Iterable[_T]) -> _T: ...
 def polynomial_derivative(coefficients: Sequence[_T]) -> list[_T]: ...
 def totient(n: int) -> int: ...
+def _shift_to_odd(n: int) -> tuple[int, int]: ...
+def _strong_probable_prime(n: int, base: int) -> bool: ...
+def is_prime(n: int) -> bool: ...
+def loops(n: int) -> Iterator[None]: ...

+ 32 - 14
contrib/python/more-itertools/py3/tests/test_more.py

@@ -3836,7 +3836,7 @@ class _FrozenMultiset(Set):
         return hash(self._collection)
 
     def __repr__(self):
-        return "FrozenSet([{}]".format(", ".join(repr(x) for x in iter(self)))
+        return f'FrozenSet([{", ".join(repr(x) for x in iter(self))}]'
 
 
 class SetPartitionsTests(TestCase):
@@ -4321,6 +4321,33 @@ class SampleTests(TestCase):
         # The observed largest difference in 10,000 simulations was 4.337999
         self.assertTrue(difference_in_means < 4.4)
 
+    def test_error_cases(self):
+
+        # weights and counts are mutally exclusive
+        with self.assertRaises(TypeError):
+            mi.sample(
+                'abcde', 3, weights=[1, 2, 3, 4, 5], counts=[1, 2, 3, 4, 5]
+            )
+
+        # Weighted sample larger than population
+        with self.assertRaises(ValueError):
+            mi.sample('abcde', 10, weights=[1, 2, 3, 4, 5], strict=True)
+
+        # Counted sample larger than population
+        with self.assertRaises(ValueError):
+            mi.sample('abcde', 10, counts=[1, 1, 1, 1, 1], strict=True)
+
+
+class BarelySortable:
+    def __init__(self, value):
+        self.value = value
+
+    def __lt__(self, other):
+        return self.value < other.value
+
+    def __int__(self):
+        return int(self.value)
+
 
 class IsSortedTests(TestCase):
     def test_basic(self):
@@ -4330,7 +4357,6 @@ class IsSortedTests(TestCase):
             ([1, 2, 3], {}, True),
             ([1, 1, 2, 3], {}, True),
             ([1, 10, 2, 3], {}, False),
-            ([3, float('nan'), 1, 2], {}, True),
             (['1', '10', '2', '3'], {}, True),
             (['1', '10', '2', '3'], {'key': int}, False),
             ([1, 2, 3], {'reverse': True}, False),
@@ -4362,17 +4388,6 @@ class IsSortedTests(TestCase):
                 {'strict': True, 'key': int, 'reverse': True},
                 False,
             ),
-            # We'll do the same weird thing as Python here
-            (['nan', 0, 'nan', 0], {'key': float}, True),
-            ([0, 'nan', 0, 'nan'], {'key': float}, True),
-            (['nan', 0, 'nan', 0], {'key': float, 'reverse': True}, True),
-            ([0, 'nan', 0, 'nan'], {'key': float, 'reverse': True}, True),
-            ([0, 'nan', 0, 'nan'], {'strict': True, 'key': float}, True),
-            (
-                ['nan', 0, 'nan', 0],
-                {'strict': True, 'key': float, 'reverse': True},
-                True,
-            ),
         ]:
             key = kwargs.get('key', None)
             reverse = kwargs.get('reverse', False)
@@ -4382,7 +4397,10 @@ class IsSortedTests(TestCase):
                 iterable=iterable, key=key, reverse=reverse, strict=strict
             ):
                 mi_result = mi.is_sorted(
-                    iter(iterable), key=key, reverse=reverse, strict=strict
+                    map(BarelySortable, iterable),
+                    key=key,
+                    reverse=reverse,
+                    strict=strict,
                 )
 
                 sorted_iterable = sorted(iterable, key=key, reverse=reverse)

+ 179 - 3
contrib/python/more-itertools/py3/tests/test_recipes.py

@@ -4,7 +4,7 @@ from fractions import Fraction
 from functools import reduce
 from itertools import combinations, count, groupby, permutations
 from operator import mul
-from math import factorial
+from math import comb, factorial
 from sys import version_info
 from unittest import TestCase, skipIf
 from unittest.mock import patch
@@ -923,6 +923,12 @@ class PolynomialFromRootsTests(TestCase):
                 actual = mi.polynomial_from_roots(roots)
                 self.assertEqual(actual, expected)
 
+    def test_large(self):
+        n = 1_500
+        actual = mi.polynomial_from_roots([-1] * n)
+        expected = [comb(n, k) for k in range(n + 1)]
+        self.assertEqual(actual, expected)
+
 
 class PolynomialEvalTests(TestCase):
     def test_basic(self):
@@ -1147,8 +1153,12 @@ class FactorTests(TestCase):
             (6, [2, 3]),
             (360, [2, 2, 2, 3, 3, 5]),
             (128_884_753_939, [128_884_753_939]),
-            (999953 * 999983, [999953, 999983]),
-            (909_909_090_909, [3, 3, 7, 13, 13, 751, 113797]),
+            (999_953 * 999_983, [999_953, 999_983]),
+            (909_909_090_909, [3, 3, 7, 13, 13, 751, 1_137_97]),
+            (
+                1_647_403_876_764_101_672_307_088,
+                [2, 2, 2, 2, 19, 23, 109471, 13571009, 158594251],
+            ),
         ):
             with self.subTest(n=n):
                 actual = list(mi.factor(n))
@@ -1209,3 +1219,169 @@ class TotientTests(TestCase):
         ):
             with self.subTest(n=n):
                 self.assertEqual(mi.totient(n), expected)
+
+
+class PrimeFunctionTests(TestCase):
+    def test_is_prime_pseudoprimes(self):
+        # Carmichael number that strong pseudoprime to prime bases < 307
+        # https://doi.org/10.1006/jsco.1995.1042
+        p = 29674495668685510550154174642905332730771991799853043350995075531276838753171770199594238596428121188033664754218345562493168782883  # noqa:E501
+        gnarly_carmichael = (313 * (p - 1) + 1) * (353 * (p - 1) + 1)
+
+        for n in (
+            # Least Carmichael number with n prime factors:
+            # https://oeis.org/A006931
+            561,
+            41041,
+            825265,
+            321197185,
+            5394826801,
+            232250619601,
+            9746347772161,
+            1436697831295441,
+            60977817398996785,
+            7156857700403137441,
+            1791562810662585767521,
+            87674969936234821377601,
+            6553130926752006031481761,
+            1590231231043178376951698401,
+            # Carmichael numbers with exactly 4 prime factors:
+            # https://oeis.org/A074379
+            41041,
+            62745,
+            63973,
+            75361,
+            101101,
+            126217,
+            172081,
+            188461,
+            278545,
+            340561,
+            449065,
+            552721,
+            656601,
+            658801,
+            670033,
+            748657,
+            838201,
+            852841,
+            997633,
+            1033669,
+            1082809,
+            1569457,
+            1773289,
+            2100901,
+            2113921,
+            2433601,
+            2455921,
+            # Lucas-Carmichael numbers:
+            # https://oeis.org/A006972
+            399,
+            935,
+            2015,
+            2915,
+            4991,
+            5719,
+            7055,
+            8855,
+            12719,
+            18095,
+            20705,
+            20999,
+            22847,
+            29315,
+            31535,
+            46079,
+            51359,
+            60059,
+            63503,
+            67199,
+            73535,
+            76751,
+            80189,
+            81719,
+            88559,
+            90287,
+            # Strong pseudoprimes to bases 2, 3 and 5:
+            # https://oeis.org/A056915
+            25326001,
+            161304001,
+            960946321,
+            1157839381,
+            3215031751,
+            3697278427,
+            5764643587,
+            6770862367,
+            14386156093,
+            15579919981,
+            18459366157,
+            19887974881,
+            21276028621,
+            27716349961,
+            29118033181,
+            37131467521,
+            41752650241,
+            42550716781,
+            43536545821,
+            # Strong pseudoprimes to bases 2, 3, 5, and 7:
+            # https://oeis.org/A211112
+            39365185894561,
+            52657210792621,
+            11377272352951,
+            15070413782971,
+            3343433905957,
+            16603327018981,
+            3461715915661,
+            52384617784801,
+            3477707481751,
+            18996486073489,
+            55712149574381,
+            gnarly_carmichael,
+        ):
+            with self.subTest(n=n):
+                self.assertFalse(mi.is_prime(n))
+
+    def test_primes(self):
+        for i, n in enumerate(mi.sieve(10**5)):
+            with self.subTest(n=n):
+                self.assertTrue(mi.is_prime(n))
+                self.assertEqual(mi.nth_prime(i), n)
+
+        self.assertFalse(mi.is_prime(-1))
+        with self.assertRaises(ValueError):
+            mi.nth_prime(-1)
+
+    def test_special_primes(self):
+        for n in (
+            # Mersenee primes:
+            # https://oeis.org/A211112
+            3,
+            7,
+            31,
+            127,
+            8191,
+            131071,
+            524287,
+            2147483647,
+            2305843009213693951,
+            618970019642690137449562111,
+            162259276829213363391578010288127,
+            170141183460469231731687303715884105727,
+            # Various big primes:
+            # https://bigprimes.org/
+            7990614013,
+            80358337843874809987,
+            814847562949580526031364519741,
+            1982427225022428178169740526258124929077,
+            91828213828508622559862344537590739566883686537727,
+            406414746815201693481517584049440077164779143248351060891669,
+        ):
+            with self.subTest(n=n):
+                self.assertTrue(mi.is_prime(n))
+
+
+class LoopsTests(TestCase):
+    def test_basic(self):
+        self.assertTrue(
+            all(list(mi.loops(n)) == [None] * n for n in range(-10, 10))
+        )

+ 1 - 1
contrib/python/more-itertools/py3/ya.make

@@ -2,7 +2,7 @@
 
 PY3_LIBRARY()
 
-VERSION(10.5.0)
+VERSION(10.6.0)
 
 LICENSE(MIT)