@@ -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
0 commit comments