@@ -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
676704The 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