@@ -127,6 +127,76 @@ def combinations2(iterable, r):
127127 self .assertEqual (len (set (map (id , combinations ('abcde' , 3 )))), 1 )
128128 self .assertNotEqual (len (set (map (id , list (combinations ('abcde' , 3 ))))), 1 )
129129
130+ def test_combinations_with_replacement (self ):
131+ cwr = combinations_with_replacement
132+ self .assertRaises (TypeError , cwr , 'abc' ) # missing r argument
133+ self .assertRaises (TypeError , cwr , 'abc' , 2 , 1 ) # too many arguments
134+ self .assertRaises (TypeError , cwr , None ) # pool is not iterable
135+ self .assertRaises (ValueError , cwr , 'abc' , - 2 ) # r is negative
136+ self .assertEqual (list (cwr ('ABC' , 2 )),
137+ [('A' ,'A' ), ('A' ,'B' ), ('A' ,'C' ), ('B' ,'B' ), ('B' ,'C' ), ('C' ,'C' )])
138+
139+ def cwr1 (iterable , r ):
140+ 'Pure python version shown in the docs'
141+ # number items returned: (n+r-1)! / r! / (n-1)! when n>0
142+ pool = tuple (iterable )
143+ n = len (pool )
144+ if not n and r :
145+ return
146+ indices = [0 ] * r
147+ yield tuple (pool [i ] for i in indices )
148+ while 1 :
149+ for i in reversed (range (r )):
150+ if indices [i ] != n - 1 :
151+ break
152+ else :
153+ return
154+ indices [i :] = [indices [i ] + 1 ] * (r - i )
155+ yield tuple (pool [i ] for i in indices )
156+
157+ def cwr2 (iterable , r ):
158+ 'Pure python version shown in the docs'
159+ pool = tuple (iterable )
160+ n = len (pool )
161+ for indices in product (range (n ), repeat = r ):
162+ if sorted (indices ) == list (indices ):
163+ yield tuple (pool [i ] for i in indices )
164+
165+ def numcombs (n , r ):
166+ if not n :
167+ return 0 if r else 1
168+ return fact (n + r - 1 ) / fact (r )/ fact (n - 1 )
169+
170+ for n in range (7 ):
171+ values = [5 * x - 12 for x in range (n )]
172+ for r in range (n + 2 ):
173+ result = list (cwr (values , r ))
174+
175+ self .assertEqual (len (result ), numcombs (n , r )) # right number of combs
176+ self .assertEqual (len (result ), len (set (result ))) # no repeats
177+ self .assertEqual (result , sorted (result )) # lexicographic order
178+
179+ regular_combs = list (combinations (values , r )) # compare to combs without replacement
180+ if n == 0 or r <= 1 :
181+ self .assertEquals (result , regular_combs ) # cases that should be identical
182+ else :
183+ self .assert_ (set (result ) >= set (regular_combs )) # rest should be supersets of regular combs
184+
185+ for c in result :
186+ self .assertEqual (len (c ), r ) # r-length combinations
187+ noruns = [k for k ,v in groupby (c )] # combo without consecutive repeats
188+ self .assertEqual (len (noruns ), len (set (noruns ))) # no repeats other than consecutive
189+ self .assertEqual (list (c ), sorted (c )) # keep original ordering
190+ self .assert_ (all (e in values for e in c )) # elements taken from input iterable
191+ self .assertEqual (noruns ,
192+ [e for e in values if e in c ]) # comb is a subsequence of the input iterable
193+ self .assertEqual (result , list (cwr1 (values , r ))) # matches first pure python version
194+ self .assertEqual (result , list (cwr2 (values , r ))) # matches second pure python version
195+
196+ # Test implementation detail: tuple re-use
197+ self .assertEqual (len (set (map (id , cwr ('abcde' , 3 )))), 1 )
198+ self .assertNotEqual (len (set (map (id , list (cwr ('abcde' , 3 ))))), 1 )
199+
130200 def test_permutations (self ):
131201 self .assertRaises (TypeError , permutations ) # too few arguments
132202 self .assertRaises (TypeError , permutations , 'abc' , 2 , 1 ) # too many arguments
@@ -716,6 +786,10 @@ def test_combinations(self):
716786 self .assertEqual (list (combinations (range (4 ), 3 )),
717787 [(0 ,1 ,2 ), (0 ,1 ,3 ), (0 ,2 ,3 ), (1 ,2 ,3 )])
718788
789+ def test_combinations_with_replacement (self ):
790+ self .assertEqual (list (combinations_with_replacement ('ABC' , 2 )),
791+ [('A' ,'A' ), ('A' ,'B' ), ('A' ,'C' ), ('B' ,'B' ), ('B' ,'C' ), ('C' ,'C' )])
792+
719793 def test_compress (self ):
720794 self .assertEqual (list (compress ('ABCDEF' , [1 ,0 ,1 ,0 ,1 ,1 ])), list ('ACEF' ))
721795
@@ -799,6 +873,10 @@ def test_combinations(self):
799873 a = []
800874 self .makecycle (combinations ([1 ,2 ,a ,3 ], 3 ), a )
801875
876+ def test_combinations_with_replacement (self ):
877+ a = []
878+ self .makecycle (combinations_with_replacement ([1 ,2 ,a ,3 ], 3 ), a )
879+
802880 def test_compress (self ):
803881 a = []
804882 self .makecycle (compress ('ABCDEF' , [1 ,0 ,1 ,0 ,1 ,0 ]), a )
@@ -1291,21 +1369,6 @@ def __init__(self, newarg=None, *args):
12911369... s = list(iterable)
12921370... return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
12931371
1294- >>> def combinations_with_replacement(iterable, r):
1295- ... "combinations_with_replacement('ABC', 3) --> AA AB AC BB BC CC"
1296- ... pool = tuple(iterable)
1297- ... n = len(pool)
1298- ... indices = [0] * r
1299- ... yield tuple(pool[i] for i in indices)
1300- ... while 1:
1301- ... for i in reversed(range(r)):
1302- ... if indices[i] != n - 1:
1303- ... break
1304- ... else:
1305- ... return
1306- ... indices[i:] = [indices[i] + 1] * (r - i)
1307- ... yield tuple(pool[i] for i in indices)
1308-
13091372>>> def unique_everseen(iterable, key=None):
13101373... "List unique elements, preserving order. Remember all elements ever seen."
13111374... # unique_everseen('AAAABBBCCDAABBB') --> A B C D
@@ -1386,29 +1449,6 @@ def __init__(self, newarg=None, *args):
13861449>>> list(powerset([1,2,3]))
13871450[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
13881451
1389- >>> list(combinations_with_replacement('abc', 2))
1390- [('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')]
1391-
1392- >>> list(combinations_with_replacement('01', 3))
1393- [('0', '0', '0'), ('0', '0', '1'), ('0', '1', '1'), ('1', '1', '1')]
1394-
1395- >>> def combinations_with_replacement2(iterable, r):
1396- ... 'Alternate version that filters from product()'
1397- ... pool = tuple(iterable)
1398- ... n = len(pool)
1399- ... for indices in product(range(n), repeat=r):
1400- ... if sorted(indices) == list(indices):
1401- ... yield tuple(pool[i] for i in indices)
1402-
1403- >>> list(combinations_with_replacement('abc', 2)) == list(combinations_with_replacement2('abc', 2))
1404- True
1405-
1406- >>> list(combinations_with_replacement('01', 3)) == list(combinations_with_replacement2('01', 3))
1407- True
1408-
1409- >>> list(combinations_with_replacement('2310', 6)) == list(combinations_with_replacement2('2310', 6))
1410- True
1411-
14121452>>> list(unique_everseen('AAAABBBCCDAABBB'))
14131453['A', 'B', 'C', 'D']
14141454
0 commit comments