Skip to content

Commit 2426c45

Browse files
authored
[3.14] gh-143762 Backport a6bc60d for random combinatoric recipes (#143764)
1 parent f663aa2 commit 2426c45

File tree

1 file changed

+89
-3
lines changed

1 file changed

+89
-3
lines changed

Doc/library/random.rst

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,11 +633,12 @@ These recipes show how to efficiently make random selections
633633
from 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+
664750
The 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
666752
representable as Python floats. However, many other representable

0 commit comments

Comments
 (0)