From 7ded35d0549a32cb87566b637bb931fdf32acb0c Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 2 Sep 2025 09:03:27 -0500 Subject: [PATCH 1/3] Add random_derangement recipe --- Doc/library/random.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index b1120b3a4d8eb4..32fc4fd92e0118 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -630,7 +630,8 @@ Recipes ------- These recipes show how to efficiently make random selections -from the combinatoric iterators in the :mod:`itertools` module: +from the combinatoric iterators in the :mod:`itertools` module +or the :pypi:`more-itertools` project: .. testcode:: import random @@ -661,6 +662,14 @@ 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 is in its original position." + seq = tuple(iterable) + while True: + perm = random_permutation(seq) + if all(p != q for p, q in zip(seq, perm)): + return perm + 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 From 36b705f4638ad2faddbea93e5cf7fb59bc45ed4f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 2 Sep 2025 09:57:23 -0500 Subject: [PATCH 2/3] Add error checking --- Doc/library/random.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index 32fc4fd92e0118..fca9f24da99d56 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -665,6 +665,8 @@ or the :pypi:`more-itertools` project: def random_derangement(iterable): "Choose a permutation where no element is in its original position." seq = tuple(iterable) + if len(seq) < 2: + raise ValueError('derangments require at least two values') while True: perm = random_permutation(seq) if all(p != q for p, q in zip(seq, perm)): From 81e682ff66b7ae5d0057ceb1e873753f0f5e014b Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 2 Sep 2025 10:45:19 -0500 Subject: [PATCH 3/3] Remove dependency on previous recipe --- Doc/library/random.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index fca9f24da99d56..e9cebf46d57b01 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -667,10 +667,11 @@ or the :pypi:`more-itertools` project: seq = tuple(iterable) if len(seq) < 2: raise ValueError('derangments require at least two values') + perm = list(seq) while True: - perm = random_permutation(seq) + random.shuffle(perm) if all(p != q for p, q in zip(seq, perm)): - return perm + return tuple(perm) 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