Skip to content

Commit b04b30f

Browse files
committed
Random derangements s/b based on position. Add tests.
1 parent 66e1399 commit b04b30f

File tree

1 file changed

+33
-5
lines changed

1 file changed

+33
-5
lines changed

Doc/library/random.rst

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -663,15 +663,43 @@ or the :pypi:`more-itertools` project:
663663
return tuple(pool[i] for i in indices)
664664

665665
def random_derangement(iterable):
666-
"Choose a permutation where no element is in its original position."
666+
"Choose a permutation where no element stays in its original position."
667667
seq = tuple(iterable)
668668
if len(seq) < 2:
669-
raise ValueError('derangements require at least two values')
670-
perm = list(seq)
669+
if not seq:
670+
return ()
671+
raise IndexError('No derangments to choose from')
672+
perm = list(range(len(seq)))
673+
start = tuple(perm)
671674
while True:
672675
random.shuffle(perm)
673-
if all(p != q for p, q in zip(seq, perm)):
674-
return tuple(perm)
676+
if all(p != q for p, q in zip(start, perm)):
677+
return tuple([seq[i] for i in perm])
678+
679+
.. doctest::
680+
:hide:
681+
682+
>>> import random
683+
>>> random.seed(8675309)
684+
>>> random_derangement('')
685+
()
686+
>>> random_derangement('A')
687+
Traceback (most recent call last):
688+
...
689+
IndexError: No derangments to choose from
690+
>>> random_derangement('AB')
691+
('B', 'A')
692+
>>> random_derangement('ABC')
693+
('C', 'A', 'B')
694+
>>> random_derangement('ABCD')
695+
('B', 'A', 'D', 'C')
696+
>>> random_derangement('ABCDE')
697+
('B', 'C', 'A', 'E', 'D')
698+
>>> # Identical inputs treated as distinct
699+
>>> identical = 20
700+
>>> random_derangement((10, identical, 30, identical))
701+
(20, 30, 10, 20)
702+
675703

676704
The default :func:`.random` returns multiples of 2⁻⁵³ in the range
677705
*0.0 ≤ x < 1.0*. All such numbers are evenly spaced and are exactly

0 commit comments

Comments
 (0)