@@ -633,11 +633,12 @@ These recipes show how to efficiently make random selections
633633from the combinatoric iterators in the :mod: `itertools ` module:
634634
635635.. testcode ::
636+
636637 import random
637638
638- def random_product(*args , repeat=1):
639- "Random selection from itertools.product(*args, **kwds )"
640- pools = [ tuple(pool) for pool in args] * repeat
639+ def random_product(*iterables , repeat=1):
640+ "Random selection from itertools.product(*iterables, repeat=repeat )"
641+ pools = tuple(map(tuple, iterables)) * repeat
641642 return tuple(map(random.choice, pools))
642643
643644 def random_permutation(iterable, r=None):
@@ -661,6 +662,91 @@ from the combinatoric iterators in the :mod:`itertools` module:
661662 indices = sorted(random.choices(range(n), k=r))
662663 return tuple(pool[i] for i in indices)
663664
665+ def random_derangement(iterable):
666+ "Choose a permutation where no element stays in its original position."
667+ seq = tuple(iterable)
668+ if len(seq) < 2:
669+ if not seq:
670+ return ()
671+ raise IndexError('No derangments to choose from')
672+ perm = list(range(len(seq)))
673+ start = tuple(perm)
674+ while True:
675+ random.shuffle(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+
684+
685+ >>> random.seed(8675309 )
686+ >>> random_product(' ABCDEFG' , repeat = 5 )
687+ ('D', 'B', 'E', 'F', 'E')
688+
689+
690+ >>> random.seed(8675309 )
691+ >>> random_permutation(' ABCDEFG' )
692+ ('D', 'B', 'E', 'C', 'G', 'A', 'F')
693+ >>> random_permutation(' ABCDEFG' , 5 )
694+ ('A', 'G', 'D', 'C', 'B')
695+
696+
697+ >>> random.seed(8675309 )
698+ >>> random_combination(' ABCDEFG' , 7 )
699+ ('A', 'B', 'C', 'D', 'E', 'F', 'G')
700+ >>> random_combination(' ABCDEFG' , 6 )
701+ ('A', 'B', 'C', 'D', 'F', 'G')
702+ >>> random_combination(' ABCDEFG' , 5 )
703+ ('A', 'B', 'C', 'E', 'F')
704+ >>> random_combination(' ABCDEFG' , 4 )
705+ ('B', 'C', 'D', 'G')
706+ >>> random_combination(' ABCDEFG' , 3 )
707+ ('B', 'E', 'G')
708+ >>> random_combination(' ABCDEFG' , 2 )
709+ ('E', 'G')
710+ >>> random_combination(' ABCDEFG' , 1 )
711+ ('C',)
712+ >>> random_combination(' ABCDEFG' , 0 )
713+ ()
714+
715+
716+ >>> random.seed(8675309 )
717+ >>> random_combination_with_replacement(' ABCDEFG' , 7 )
718+ ('B', 'C', 'D', 'E', 'E', 'E', 'G')
719+ >>> random_combination_with_replacement(' ABCDEFG' , 3 )
720+ ('A', 'B', 'E')
721+ >>> random_combination_with_replacement(' ABCDEFG' , 2 )
722+ ('A', 'G')
723+ >>> random_combination_with_replacement(' ABCDEFG' , 1 )
724+ ('E',)
725+ >>> random_combination_with_replacement(' ABCDEFG' , 0 )
726+ ()
727+
728+
729+ >>> random.seed(8675309 )
730+ >>> random_derangement(' ' )
731+ ()
732+ >>> random_derangement(' A' )
733+ Traceback (most recent call last):
734+ ...
735+ IndexError: No derangments to choose from
736+ >>> random_derangement(' AB' )
737+ ('B', 'A')
738+ >>> random_derangement(' ABC' )
739+ ('C', 'A', 'B')
740+ >>> random_derangement(' ABCD' )
741+ ('B', 'A', 'D', 'C')
742+ >>> random_derangement(' ABCDE' )
743+ ('B', 'C', 'A', 'E', 'D')
744+ >>> # Identical inputs treated as distinct
745+ >>> identical = 20
746+ >>> random_derangement((10 , identical, 30 , identical))
747+ (20, 30, 10, 20)
748+
749+
664750The default :func: `.random ` returns multiples of 2⁻⁵³ in the range
665751*0.0 ≤ x < 1.0 *. All such numbers are evenly spaced and are exactly
666752representable as Python floats. However, many other representable
0 commit comments