One way to create partitions is to iterate over your iterator, and then compare each multiset * with the previous one. I tested 4 ways ** to compare multipliers, and the fastest I found was to check the in membership of the iterator of the previous multiset, which is consumed, and short circuits after completing the membership test. If the number of identical elements in the multiset and the previous multiset is equal to the length of the multiset minus 1, then the criteria for their grouping are satisfied. Then, the resulting list output generator is created, where you append elements that match the criteria of the previous list , and start a new list containing tuple otherwise, yield time to minimize memory usage:
import itertools def f(n,k): prev, group = None, [] for multiset in itertools.combinations_with_replacement(range(n),k): if prev: it = iter(prev) for idx, item in enumerate(multiset): if item not in it: break if idx == len(multiset) - 1: group.append(multiset) continue if group: yield group group = [multiset] prev = multiset yield group
Testing
Input:
for item in f(4,4): print(item)
Output:
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)] [(0, 0, 1, 1), (0, 0, 1, 2), (0, 0, 1, 3)] [(0, 0, 2, 2), (0, 0, 2, 3)] [(0, 0, 3, 3)] [(0, 1, 1, 1), (0, 1, 1, 2), (0, 1, 1, 3)] [(0, 1, 2, 2), (0, 1, 2, 3)] [(0, 1, 3, 3)] [(0, 2, 2, 2), (0, 2, 2, 3)] [(0, 2, 3, 3), (0, 3, 3, 3)] [(1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3)] [(1, 1, 2, 2), (1, 1, 2, 3)] [(1, 1, 3, 3)] [(1, 2, 2, 2), (1, 2, 2, 3)] [(1, 2, 3, 3), (1, 3, 3, 3)] [(2, 2, 2, 2), (2, 2, 2, 3)] [(2, 2, 3, 3), (2, 3, 3, 3)] [(3, 3, 3, 3)]
Input:
for item in f(5,3): print(item)
Output:
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4)] [(0, 1, 1), (0, 1, 2), (0, 1, 3), (0, 1, 4)] [(0, 2, 2), (0, 2, 3), (0, 2, 4)] [(0, 3, 3), (0, 3, 4)] [(0, 4, 4)] [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)] [(1, 2, 2), (1, 2, 3), (1, 2, 4)] [(1, 3, 3), (1, 3, 4)] [(1, 4, 4)] [(2, 2, 2), (2, 2, 3), (2, 2, 4)] [(2, 3, 3), (2, 3, 4)] [(2, 4, 4)] [(3, 3, 3), (3, 3, 4)] [(3, 4, 4), (4, 4, 4)]
* I call them multisets according to your terminology, but they are actually tuple (ordered and immutable data structures); using the collections.Counter object, for example Counter((0, 0, 0, 1)) return Counter({0: 3, 1: 1}) , and the decrement will look like a true multi-position approach, but I found it to be slower, because using order is really useful.
** Other slower functions that give the same result as I tested:
def f2(n,k): prev, group = None, [] for multiset in itertools.combinations_with_replacement(range(n),k): if prev: if sum(item1 == item2 for item1, item2 in zip(prev,multiset)) == len(multiset) - 1: group.append(multiset) continue if group: yield group group = [multiset] prev = multiset yield group def f3(n,k): prev, group = None, [] for multiset in itertools.combinations_with_replacement(range(n),k): if prev: lst = list(prev) for item in multiset: if item in lst: lst.remove(item) else: break if len(multiset) - len(lst) == len(multiset) - 1: group.append(multiset) continue if group: yield group group = [multiset] prev = multiset yield group import collections def f4(n,k): prev, group = None, [] for multiset in itertools.combinations_with_replacement(range(n),k): if prev: if sum((collections.Counter(prev) - collections.Counter(multiset)).values()) == 1: group.append(multiset) continue if group: yield group group = [multiset] prev = multiset yield group
Timing example:
from timeit import timeit list(f(11,10)) == list(f2(11,10)) == list(f3(11,10)) == list(f4(11,10)) # True timeit(lambda: list(f(11,10)), number = 10) # 4.19157001003623 timeit(lambda: list(f2(11,10)), number = 10) # 7.32002648897469 timeit(lambda: list(f3(11,10)), number = 10) # 6.236868146806955 timeit(lambda: list(f4(11,10)), number = 10) # 47.20136355608702
Note that all approaches become slow with large values ββof n and k due to the large number of generated combinations.