Search code examples
pythonpython-3.xpython-itertools

itertools.combinations: Consume multiple times over intervals


from itertools import combinations
from more_itertools import consume

k = 170
index_list = [16, 32, 48, 62, 76, 88, 100, 110, 120, 128, 136, 142, 148, 153, 158, 161, 164, 166]

for n in range(1, k+1):
    iterable = combinations(range(k), n)
    for output in iterable:
        print(output)

I am having iterables and a list containing indexes as shown above: The index_list is representing intervals: they go from 0 to 16, from 17 to 32, from 33 to 48 and so on. Last interval is 167 to k-1. When I loop a iterable, I want to skip multiple steps, whenever there are 2 values of the output within the same interval. Output (0, 1), for example: Both values are within the interval 0-16, so next output shall be (0, 17). The outputs afterwards would be (0, 18), (0, 19),..., (0, k-1) as these are not within a interval. Afterwards, the output will be (1, 2) which shall be skipped again and so on. And later, when the output is (17, 18) it shall skip to (17, 33). When n is 3, the very first output is (0, 1, 2) which then shall be skipped to (0, 17, 33). The consume-method provided by more-itertools allows one to step multiple elements ahead. Here it would be used like so:

consume(iterable, 20)    # skipping 20 steps ahead

I have managed it to get the wanted behavior for 2 elements in the output:

steps = 0
for i, out in enumerate(output):
    for j, index in enumerate(index_list):
        if out <= index:
            break
    try:
        if output[i + 1] <= index_list[j]:
            steps = steps + index_list[j] - output[i + 1]
    except:
        pass
consume(iterable, steps)

But for 3 elements and more the amount of steps is not calculated correctly anymore. It must be multiplied by some value but I don't know where to get it. Another task is, that I do not want to check for the intervals for every single output. When a consume was executed, it somehow must already know when the next consume-instruction will happen. Any help please?


Solution

  • This code only works when n < 20 (Because the number of statically nested blocks in Python is limited to 20)

    k = 170
    index_list = [16, 32, 48, 62, 76, 88, 100, 110, 120, 128, 136, 142, 148, 153, 158, 161, 164, 166]
    
    
    def get_bound(x):
        for i in range(len(index_list)):
            if x <= index_list[i]:
                return index_list[i]
        return k
    
    
    def valid_combs(i, n=None, comb=None):
        n = n if n is not None else i
        comb = comb or list(range(n))
        if i == n:
            for x in range(k):
                comb[0] = x
                yield from valid_combs(i - 1, n, comb)
        elif i >= 1:
            prev = comb[n - i - 1]
            bound = get_bound(prev)
            for x in list(range(bound + 1, k)):
                comb[n - i] = x   
                yield from valid_combs(i - 1, n, comb)
        else:
            yield tuple(comb)
    
    
    for n in range(1, k+1):
        iterable = valid_combs(n)
        for output in iterable:
            print(output)