Add itertools recipe for directly finding the n-th combination (GH-5161) (#5174)

(cherry picked from commit d37258dd2e)
This commit is contained in:
Miss Islington (bot) 2018-01-13 11:21:15 -08:00 committed by Raymond Hettinger
parent 29b1aff718
commit cf4cd4bccb
2 changed files with 56 additions and 0 deletions

View file

@ -859,6 +859,29 @@ which incur interpreter overhead.
indices = sorted(random.randrange(n) for i in range(r)) indices = sorted(random.randrange(n) for i in range(r))
return tuple(pool[i] for i in indices) return tuple(pool[i] for i in indices)
def nth_combination(iterable, r, index):
'Equivalent to list(combinations(iterable, r))[index]'
pool = tuple(iterable)
n = len(pool)
if r < 0 or r > n:
raise ValueError
c = 1
k = min(r, n-r)
for i in range(1, k+1):
c = c * (n - k + i) // i
if index < 0:
index += c
if index < 0 or index >= c:
raise IndexError
result = []
while r:
c, n, r = c*r//n, n-1, r-1
while index >= c:
index -= c
c, n = c*(n-r)//n, n-1
result.append(pool[-1-n])
return tuple(result)
Note, many of the above recipes can be optimized by replacing global lookups Note, many of the above recipes can be optimized by replacing global lookups
with local variables defined as default values. For example, the with local variables defined as default values. For example, the
*dotproduct* recipe can be written as:: *dotproduct* recipe can be written as::

View file

@ -2229,6 +2229,30 @@ Samuele
... # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x ... # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
... return next(filter(pred, iterable), default) ... return next(filter(pred, iterable), default)
>>> def nth_combination(iterable, r, index):
... 'Equivalent to list(combinations(iterable, r))[index]'
... pool = tuple(iterable)
... n = len(pool)
... if r < 0 or r > n:
... raise ValueError
... c = 1
... k = min(r, n-r)
... for i in range(1, k+1):
... c = c * (n - k + i) // i
... if index < 0:
... index += c
... if index < 0 or index >= c:
... raise IndexError
... result = []
... while r:
... c, n, r = c*r//n, n-1, r-1
... while index >= c:
... index -= c
... c, n = c*(n-r)//n, n-1
... result.append(pool[-1-n])
... return tuple(result)
This is not part of the examples but it tests to make sure the definitions This is not part of the examples but it tests to make sure the definitions
perform as purported. perform as purported.
@ -2312,6 +2336,15 @@ True
>>> first_true('ABC0DEF1', '9', str.isdigit) >>> first_true('ABC0DEF1', '9', str.isdigit)
'0' '0'
>>> population = 'ABCDEFGH'
>>> for r in range(len(population) + 1):
... seq = list(combinations(population, r))
... for i in range(len(seq)):
... assert nth_combination(population, r, i) == seq[i]
... for i in range(-len(seq), 0):
... assert nth_combination(population, r, i) == seq[i]
""" """
__test__ = {'libreftest' : libreftest} __test__ = {'libreftest' : libreftest}