]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-143762 Backport a6bc60d for random combinatoric recipes (#143764)
authorRaymond Hettinger <rhettinger@users.noreply.github.com>
Tue, 13 Jan 2026 00:19:57 +0000 (18:19 -0600)
committerGitHub <noreply@github.com>
Tue, 13 Jan 2026 00:19:57 +0000 (18:19 -0600)
Doc/library/random.rst

index dd6b56106afe98196d94f79d9f6e3bd5f251482e..fdc14cd04e2230ff4480c7edf8d9f64cdb24ad57 100644 (file)
@@ -633,11 +633,12 @@ These recipes show how to efficiently make random selections
 from the combinatoric iterators in the :mod:`itertools` module:
 
 .. testcode::
+
    import random
 
-   def random_product(*args, repeat=1):
-       "Random selection from itertools.product(*args, **kwds)"
-       pools = [tuple(pool) for pool in args] * repeat
+   def random_product(*iterables, repeat=1):
+       "Random selection from itertools.product(*iterables, repeat=repeat)"
+       pools = tuple(map(tuple, iterables)) * repeat
        return tuple(map(random.choice, pools))
 
    def random_permutation(iterable, r=None):
@@ -661,6 +662,91 @@ from the combinatoric iterators in the :mod:`itertools` module:
        indices = sorted(random.choices(range(n), k=r))
        return tuple(pool[i] for i in indices)
 
+   def random_derangement(iterable):
+       "Choose a permutation where no element stays in its original position."
+       seq = tuple(iterable)
+       if len(seq) < 2:
+           if not seq:
+               return ()
+           raise IndexError('No derangments to choose from')
+       perm = list(range(len(seq)))
+       start = tuple(perm)
+       while True:
+           random.shuffle(perm)
+           if all(p != q for p, q in zip(start, perm)):
+               return tuple([seq[i] for i in perm])
+
+.. doctest::
+    :hide:
+
+    >>> import random
+
+
+    >>> random.seed(8675309)
+    >>> random_product('ABCDEFG', repeat=5)
+    ('D', 'B', 'E', 'F', 'E')
+
+
+    >>> random.seed(8675309)
+    >>> random_permutation('ABCDEFG')
+    ('D', 'B', 'E', 'C', 'G', 'A', 'F')
+    >>> random_permutation('ABCDEFG', 5)
+    ('A', 'G', 'D', 'C', 'B')
+
+
+    >>> random.seed(8675309)
+    >>> random_combination('ABCDEFG', 7)
+    ('A', 'B', 'C', 'D', 'E', 'F', 'G')
+    >>> random_combination('ABCDEFG', 6)
+    ('A', 'B', 'C', 'D', 'F', 'G')
+    >>> random_combination('ABCDEFG', 5)
+    ('A', 'B', 'C', 'E', 'F')
+    >>> random_combination('ABCDEFG', 4)
+    ('B', 'C', 'D', 'G')
+    >>> random_combination('ABCDEFG', 3)
+    ('B', 'E', 'G')
+    >>> random_combination('ABCDEFG', 2)
+    ('E', 'G')
+    >>> random_combination('ABCDEFG', 1)
+    ('C',)
+    >>> random_combination('ABCDEFG', 0)
+    ()
+
+
+    >>> random.seed(8675309)
+    >>> random_combination_with_replacement('ABCDEFG', 7)
+    ('B', 'C', 'D', 'E', 'E', 'E', 'G')
+    >>> random_combination_with_replacement('ABCDEFG', 3)
+    ('A', 'B', 'E')
+    >>> random_combination_with_replacement('ABCDEFG', 2)
+    ('A', 'G')
+    >>> random_combination_with_replacement('ABCDEFG', 1)
+    ('E',)
+    >>> random_combination_with_replacement('ABCDEFG', 0)
+    ()
+
+
+    >>> random.seed(8675309)
+    >>> random_derangement('')
+    ()
+    >>> random_derangement('A')
+    Traceback (most recent call last):
+    ...
+    IndexError: No derangments to choose from
+    >>> random_derangement('AB')
+    ('B', 'A')
+    >>> random_derangement('ABC')
+    ('C', 'A', 'B')
+    >>> random_derangement('ABCD')
+    ('B', 'A', 'D', 'C')
+    >>> random_derangement('ABCDE')
+    ('B', 'C', 'A', 'E', 'D')
+    >>> # Identical inputs treated as distinct
+    >>> identical = 20
+    >>> random_derangement((10, identical, 30, identical))
+    (20, 30, 10, 20)
+
+
 The default :func:`.random` returns multiples of 2⁻⁵³ in the range
 *0.0 ≤ x < 1.0*.  All such numbers are evenly spaced and are exactly
 representable as Python floats.  However, many other representable