From b04b30fb81d2cf67c8eb5c02ef6d4e68decbdaab Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 12 Jan 2026 16:21:49 -0600 Subject: [PATCH 1/5] Random derangements s/b based on position. Add tests. --- Doc/library/random.rst | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index 6bddf575a809a1..d2e62516a09899 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -663,15 +663,43 @@ or the :pypi:`more-itertools` project: return tuple(pool[i] for i in indices) def random_derangement(iterable): - "Choose a permutation where no element is in its original position." + "Choose a permutation where no element stays in its original position." seq = tuple(iterable) if len(seq) < 2: - raise ValueError('derangements require at least two values') - perm = list(seq) + 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(seq, perm)): - return tuple(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_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 From 2c2775b0e4b7a8a45d2153083ea16f7b9f330950 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 12 Jan 2026 16:26:08 -0600 Subject: [PATCH 2/5] Fix signature of random_product to: *iterables --- Doc/library/random.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index d2e62516a09899..cad78b0142cd6a 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -636,9 +636,9 @@ or the :pypi:`more-itertools` project: .. testcode:: import random - def random_product(*args, repeat=1): + def random_product(*iterables, repeat=1): "Random selection from itertools.product(*args, **kwds)" - pools = [tuple(pool) for pool in args] * repeat + pools = tuple(map(tuple, iterables)) * repeat return tuple(map(random.choice, pools)) def random_permutation(iterable, r=None): @@ -680,6 +680,13 @@ or the :pypi:`more-itertools` project: :hide: >>> import random + + + >>> random.seed(8675309) + >>> random_product('ABCDEFG', repeat=5) + ('D', 'B', 'E', 'F', 'E') + + >>> random.seed(8675309) >>> random_derangement('') () From a5d0d234454c146ef056a271e71c287090d8e64c Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 12 Jan 2026 17:31:51 -0600 Subject: [PATCH 3/5] Add more tests --- Doc/library/random.rst | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index cad78b0142cd6a..adac489ecdba4a 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -687,6 +687,45 @@ or the :pypi:`more-itertools` project: ('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('') () From 0727771bef5b65dcfe90f37f077cf15f9ce7a33e Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 12 Jan 2026 17:43:41 -0600 Subject: [PATCH 4/5] Sync the code equivalent signatures --- Doc/library/random.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index adac489ecdba4a..bc83d591f9b5e5 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -637,7 +637,7 @@ or the :pypi:`more-itertools` project: import random def random_product(*iterables, repeat=1): - "Random selection from itertools.product(*args, **kwds)" + "Random selection from itertools.product(*iterables, repeat=repeat)" pools = tuple(map(tuple, iterables)) * repeat return tuple(map(random.choice, pools)) From 8d5e971af0eba3f265bda3adc4c22e64a7ccc2c7 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 12 Jan 2026 17:45:10 -0600 Subject: [PATCH 5/5] . --- Doc/library/random.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index bc83d591f9b5e5..4c37a69079dcd6 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -634,6 +634,7 @@ from the combinatoric iterators in the :mod:`itertools` module or the :pypi:`more-itertools` project: .. testcode:: + import random def random_product(*iterables, repeat=1):