Skip to content

Commit a6bc60d

Browse files
authored
Update random combinatoric recipes and add tests (gh-143762)
1 parent 298d544 commit a6bc60d

File tree

1 file changed

+83
-8
lines changed

1 file changed

+83
-8
lines changed

Doc/library/random.rst

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -634,11 +634,12 @@ from the combinatoric iterators in the :mod:`itertools` module
634634
or the :pypi:`more-itertools` project:
635635

636636
.. testcode::
637+
637638
import random
638639

639-
def random_product(*args, repeat=1):
640-
"Random selection from itertools.product(*args, **kwds)"
641-
pools = [tuple(pool) for pool in args] * repeat
640+
def random_product(*iterables, repeat=1):
641+
"Random selection from itertools.product(*iterables, repeat=repeat)"
642+
pools = tuple(map(tuple, iterables)) * repeat
642643
return tuple(map(random.choice, pools))
643644
644645
def random_permutation(iterable, r=None):
@@ -663,15 +664,89 @@ or the :pypi:`more-itertools` project:
663664
return tuple(pool[i] for i in indices)
664665

665666
def random_derangement(iterable):
666-
"Choose a permutation where no element is in its original position."
667+
"Choose a permutation where no element stays in its original position."
667668
seq = tuple(iterable)
668669
if len(seq) < 2:
669-
raise ValueError('derangements require at least two values')
670-
perm = list(seq)
670+
if not seq:
671+
return ()
672+
raise IndexError('No derangments to choose from')
673+
perm = list(range(len(seq)))
674+
start = tuple(perm)
671675
while True:
672676
random.shuffle(perm)
673-
if all(p != q for p, q in zip(seq, perm)):
674-
return tuple(perm)
677+
if all(p != q for p, q in zip(start, perm)):
678+
return tuple([seq[i] for i in perm])
679+
680+
.. doctest::
681+
:hide:
682+
683+
>>> import random
684+
685+
686+
>>> random.seed(8675309)
687+
>>> random_product('ABCDEFG', repeat=5)
688+
('D', 'B', 'E', 'F', 'E')
689+
690+
691+
>>> random.seed(8675309)
692+
>>> random_permutation('ABCDEFG')
693+
('D', 'B', 'E', 'C', 'G', 'A', 'F')
694+
>>> random_permutation('ABCDEFG', 5)
695+
('A', 'G', 'D', 'C', 'B')
696+
697+
698+
>>> random.seed(8675309)
699+
>>> random_combination('ABCDEFG', 7)
700+
('A', 'B', 'C', 'D', 'E', 'F', 'G')
701+
>>> random_combination('ABCDEFG', 6)
702+
('A', 'B', 'C', 'D', 'F', 'G')
703+
>>> random_combination('ABCDEFG', 5)
704+
('A', 'B', 'C', 'E', 'F')
705+
>>> random_combination('ABCDEFG', 4)
706+
('B', 'C', 'D', 'G')
707+
>>> random_combination('ABCDEFG', 3)
708+
('B', 'E', 'G')
709+
>>> random_combination('ABCDEFG', 2)
710+
('E', 'G')
711+
>>> random_combination('ABCDEFG', 1)
712+
('C',)
713+
>>> random_combination('ABCDEFG', 0)
714+
()
715+
716+
717+
>>> random.seed(8675309)
718+
>>> random_combination_with_replacement('ABCDEFG', 7)
719+
('B', 'C', 'D', 'E', 'E', 'E', 'G')
720+
>>> random_combination_with_replacement('ABCDEFG', 3)
721+
('A', 'B', 'E')
722+
>>> random_combination_with_replacement('ABCDEFG', 2)
723+
('A', 'G')
724+
>>> random_combination_with_replacement('ABCDEFG', 1)
725+
('E',)
726+
>>> random_combination_with_replacement('ABCDEFG', 0)
727+
()
728+
729+
730+
>>> random.seed(8675309)
731+
>>> random_derangement('')
732+
()
733+
>>> random_derangement('A')
734+
Traceback (most recent call last):
735+
...
736+
IndexError: No derangments to choose from
737+
>>> random_derangement('AB')
738+
('B', 'A')
739+
>>> random_derangement('ABC')
740+
('C', 'A', 'B')
741+
>>> random_derangement('ABCD')
742+
('B', 'A', 'D', 'C')
743+
>>> random_derangement('ABCDE')
744+
('B', 'C', 'A', 'E', 'D')
745+
>>> # Identical inputs treated as distinct
746+
>>> identical = 20
747+
>>> random_derangement((10, identical, 30, identical))
748+
(20, 30, 10, 20)
749+
675750

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

0 commit comments

Comments
 (0)