Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d07d939

Browse files
committed
Forward port r69001: itertools.combinations_with_replacement().
1 parent dd1b33a commit d07d939

5 files changed

Lines changed: 379 additions & 59 deletions

File tree

Doc/library/collections.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,7 @@ counts less than one::
279279
Section 4.6.3, Exercise 19*\.
280280

281281
* To enumerate all distinct multisets of a given size over a given set of
282-
elements, see :func:`combinations_with_replacement` in the
283-
:ref:`itertools-recipes` for itertools::
282+
elements, see :func:`itertools.combinations_with_replacement`.
284283

285284
map(Counter, combinations_with_replacement('ABC', 2)) --> AA AB AC BB BC CC
286285

Doc/library/itertools.rst

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,53 @@ loops that truncate the stream.
133133
The number of items returned is ``n! / r! / (n-r)!`` when ``0 <= r <= n``
134134
or zero when ``r > n``.
135135

136+
.. function:: combinations_with_replacement(iterable, r)
137+
138+
Return *r* length subsequences of elements from the input *iterable*
139+
allowing individual elements to be repeated more than once.
140+
141+
Combinations are emitted in lexicographic sort order. So, if the
142+
input *iterable* is sorted, the combination tuples will be produced
143+
in sorted order.
144+
145+
Elements are treated as unique based on their position, not on their
146+
value. So if the input elements are unique, the generated combinations
147+
will also be unique.
148+
149+
Equivalent to::
150+
151+
def combinations_with_replacement(iterable, r):
152+
# combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
153+
pool = tuple(iterable)
154+
n = len(pool)
155+
if not n and r:
156+
return
157+
indices = [0] * r
158+
yield tuple(pool[i] for i in indices)
159+
while 1:
160+
for i in reversed(range(r)):
161+
if indices[i] != n - 1:
162+
break
163+
else:
164+
return
165+
indices[i:] = [indices[i] + 1] * (r - i)
166+
yield tuple(pool[i] for i in indices)
167+
168+
The code for :func:`combinations_with_replacement` can be also expressed as
169+
a subsequence of :func:`product` after filtering entries where the elements
170+
are not in sorted order (according to their position in the input pool)::
171+
172+
def combinations_with_replacement(iterable, r):
173+
pool = tuple(iterable)
174+
n = len(pool)
175+
for indices in product(range(n), repeat=r):
176+
if sorted(indices) == list(indices):
177+
yield tuple(pool[i] for i in indices)
178+
179+
The number of items returned is ``(n+r-1)! / r! / (n-1)!`` when ``n > 0``.
180+
181+
.. versionadded:: 2.7
182+
136183
.. function:: compress(data, selectors)
137184

138185
Make an iterator that filters elements from *data* returning only those that
@@ -608,22 +655,6 @@ which incur interpreter overhead.
608655
s = list(iterable)
609656
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
610657

611-
def combinations_with_replacement(iterable, r):
612-
"combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC"
613-
# number items returned: (n+r-1)! / r! / (n-1)!
614-
pool = tuple(iterable)
615-
n = len(pool)
616-
indices = [0] * r
617-
yield tuple(pool[i] for i in indices)
618-
while True:
619-
for i in reversed(range(r)):
620-
if indices[i] != n - 1:
621-
break
622-
else:
623-
return
624-
indices[i:] = [indices[i] + 1] * (r - i)
625-
yield tuple(pool[i] for i in indices)
626-
627658
def unique_everseen(iterable, key=None):
628659
"List unique elements, preserving order. Remember all elements ever seen."
629660
# unique_everseen('AAAABBBCCDAABBB') --> A B C D

Lib/test/test_itertools.py

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,76 @@ def combinations2(iterable, r):
131131
self.assertEqual(len(set(map(id, combinations('abcde', 3)))), 1)
132132
self.assertNotEqual(len(set(map(id, list(combinations('abcde', 3))))), 1)
133133

134+
def test_combinations_with_replacement(self):
135+
cwr = combinations_with_replacement
136+
self.assertRaises(TypeError, cwr, 'abc') # missing r argument
137+
self.assertRaises(TypeError, cwr, 'abc', 2, 1) # too many arguments
138+
self.assertRaises(TypeError, cwr, None) # pool is not iterable
139+
self.assertRaises(ValueError, cwr, 'abc', -2) # r is negative
140+
self.assertEqual(list(cwr('ABC', 2)),
141+
[('A','A'), ('A','B'), ('A','C'), ('B','B'), ('B','C'), ('C','C')])
142+
143+
def cwr1(iterable, r):
144+
'Pure python version shown in the docs'
145+
# number items returned: (n+r-1)! / r! / (n-1)! when n>0
146+
pool = tuple(iterable)
147+
n = len(pool)
148+
if not n and r:
149+
return
150+
indices = [0] * r
151+
yield tuple(pool[i] for i in indices)
152+
while 1:
153+
for i in reversed(range(r)):
154+
if indices[i] != n - 1:
155+
break
156+
else:
157+
return
158+
indices[i:] = [indices[i] + 1] * (r - i)
159+
yield tuple(pool[i] for i in indices)
160+
161+
def cwr2(iterable, r):
162+
'Pure python version shown in the docs'
163+
pool = tuple(iterable)
164+
n = len(pool)
165+
for indices in product(range(n), repeat=r):
166+
if sorted(indices) == list(indices):
167+
yield tuple(pool[i] for i in indices)
168+
169+
def numcombs(n, r):
170+
if not n:
171+
return 0 if r else 1
172+
return fact(n+r-1) / fact(r)/ fact(n-1)
173+
174+
for n in range(7):
175+
values = [5*x-12 for x in range(n)]
176+
for r in range(n+2):
177+
result = list(cwr(values, r))
178+
179+
self.assertEqual(len(result), numcombs(n, r)) # right number of combs
180+
self.assertEqual(len(result), len(set(result))) # no repeats
181+
self.assertEqual(result, sorted(result)) # lexicographic order
182+
183+
regular_combs = list(combinations(values, r)) # compare to combs without replacement
184+
if n == 0 or r <= 1:
185+
self.assertEquals(result, regular_combs) # cases that should be identical
186+
else:
187+
self.assert_(set(result) >= set(regular_combs)) # rest should be supersets of regular combs
188+
189+
for c in result:
190+
self.assertEqual(len(c), r) # r-length combinations
191+
noruns = [k for k,v in groupby(c)] # combo without consecutive repeats
192+
self.assertEqual(len(noruns), len(set(noruns))) # no repeats other than consecutive
193+
self.assertEqual(list(c), sorted(c)) # keep original ordering
194+
self.assert_(all(e in values for e in c)) # elements taken from input iterable
195+
self.assertEqual(noruns,
196+
[e for e in values if e in c]) # comb is a subsequence of the input iterable
197+
self.assertEqual(result, list(cwr1(values, r))) # matches first pure python version
198+
self.assertEqual(result, list(cwr2(values, r))) # matches second pure python version
199+
200+
# Test implementation detail: tuple re-use
201+
self.assertEqual(len(set(map(id, cwr('abcde', 3)))), 1)
202+
self.assertNotEqual(len(set(map(id, list(cwr('abcde', 3))))), 1)
203+
134204
def test_permutations(self):
135205
self.assertRaises(TypeError, permutations) # too few arguments
136206
self.assertRaises(TypeError, permutations, 'abc', 2, 1) # too many arguments
@@ -730,6 +800,10 @@ def test_combinations(self):
730800
self.assertEqual(list(combinations(range(4), 3)),
731801
[(0,1,2), (0,1,3), (0,2,3), (1,2,3)])
732802

803+
def test_combinations_with_replacement(self):
804+
self.assertEqual(list(combinations_with_replacement('ABC', 2)),
805+
[('A','A'), ('A','B'), ('A','C'), ('B','B'), ('B','C'), ('C','C')])
806+
733807
def test_compress(self):
734808
self.assertEqual(list(compress('ABCDEF', [1,0,1,0,1,1])), list('ACEF'))
735809

@@ -813,6 +887,10 @@ def test_combinations(self):
813887
a = []
814888
self.makecycle(combinations([1,2,a,3], 3), a)
815889

890+
def test_combinations_with_replacement(self):
891+
a = []
892+
self.makecycle(combinations_with_replacement([1,2,a,3], 3), a)
893+
816894
def test_compress(self):
817895
a = []
818896
self.makecycle(compress('ABCDEF', [1,0,1,0,1,0]), a)
@@ -1312,21 +1390,6 @@ def __init__(self, newarg=None, *args):
13121390
... s = list(iterable)
13131391
... return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
13141392
1315-
>>> def combinations_with_replacement(iterable, r):
1316-
... "combinations_with_replacement('ABC', 3) --> AA AB AC BB BC CC"
1317-
... pool = tuple(iterable)
1318-
... n = len(pool)
1319-
... indices = [0] * r
1320-
... yield tuple(pool[i] for i in indices)
1321-
... while 1:
1322-
... for i in reversed(range(r)):
1323-
... if indices[i] != n - 1:
1324-
... break
1325-
... else:
1326-
... return
1327-
... indices[i:] = [indices[i] + 1] * (r - i)
1328-
... yield tuple(pool[i] for i in indices)
1329-
13301393
>>> def unique_everseen(iterable, key=None):
13311394
... "List unique elements, preserving order. Remember all elements ever seen."
13321395
... # unique_everseen('AAAABBBCCDAABBB') --> A B C D
@@ -1407,29 +1470,6 @@ def __init__(self, newarg=None, *args):
14071470
>>> list(powerset([1,2,3]))
14081471
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
14091472
1410-
>>> list(combinations_with_replacement('abc', 2))
1411-
[('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')]
1412-
1413-
>>> list(combinations_with_replacement('01', 3))
1414-
[('0', '0', '0'), ('0', '0', '1'), ('0', '1', '1'), ('1', '1', '1')]
1415-
1416-
>>> def combinations_with_replacement2(iterable, r):
1417-
... 'Alternate version that filters from product()'
1418-
... pool = tuple(iterable)
1419-
... n = len(pool)
1420-
... for indices in product(range(n), repeat=r):
1421-
... if sorted(indices) == list(indices):
1422-
... yield tuple(pool[i] for i in indices)
1423-
1424-
>>> list(combinations_with_replacement('abc', 2)) == list(combinations_with_replacement2('abc', 2))
1425-
True
1426-
1427-
>>> list(combinations_with_replacement('01', 3)) == list(combinations_with_replacement2('01', 3))
1428-
True
1429-
1430-
>>> list(combinations_with_replacement('2310', 6)) == list(combinations_with_replacement2('2310', 6))
1431-
True
1432-
14331473
>>> list(unique_everseen('AAAABBBCCDAABBB'))
14341474
['A', 'B', 'C', 'D']
14351475

Misc/NEWS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ Library
153153

154154
- Issue #4863: distutils.mwerkscompiler has been removed.
155155

156-
- Added a new function: itertools.compress().
156+
- Added a new itertools functions: combinations_with_replacement()
157+
and compress().
157158

158159
- Fix and properly document the multiprocessing module's logging
159160
support, expose the internal levels and provide proper usage

0 commit comments

Comments
 (0)