Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using itertools for arbitrary number of nested loops of different ranges with dependencies?

Given a list of upperbounds: B1, B2, .. BN;
Dependency Functions: f1, ..., fN-1,

I'm wondering if there's a recipe using itertools or other classes in python for:

for i1 in range(0, B1):  
    for i2 in range(f1(i1), B2): ...
         for iN in range(fN-1(iN-1), BN)
             dostuff(i1, i2, ... iN)

Where there are N levels of nesting?
I want to use this helper function like this:
dependentProducts(Bs, fs, dostuff),
which returns a list or iterable

Ideally, the implementation would be iterative instead of recursive.

like image 810
qedpi Avatar asked Aug 23 '16 19:08

qedpi


People also ask

Can you have 3 nested loops?

There is no limitation on the chaining of loops. In the nested loop, the number of iterations will be equal to the number of iterations in the outer loop multiplied by the iterations in the inner loop. In each iteration of the outer loop inner loop execute all its iteration.

Is Itertools product faster than nested for loop?

product was significantly slower than my nested for loops. The results from profiling (based on 10 runs per method) was an average of ~0.8 seconds for the nested for loop approach and ~1.3 seconds for the itertools. product approach.

How to use nested for loop in Python?

In Python programming language there are two types of loops which are for loop and while loop. Using these loops we can create nested loops in Python. Nested loops mean loops inside a loop. For example, while loop inside the for loop, for loop inside the for loop, etc.

Can you have multiple for loops in Python?

Nested For LoopsLoops can be nested in Python, as they can with other programming languages. The program first encounters the outer loop, executing its first iteration. This first iteration triggers the inner, nested loop, which then runs to completion.


2 Answers

An iterative solution using @LaurentLAPORTE's setup. Put this code right under his and it should work. My args is a stack of the arguments fed into dostuff whenever it's full. The actual solution is the middle part, top and bottom parts are just testing.

stefan = []
def dostuff(*args):
    stefan.append(list(args))

args = [-1]
while args:
    n = len(args)
    args[-1] += 1
    if args[-1] >= B[n-1]:
        args.pop()
    elif n == len(B):
        dostuff(*args)
    else:
        args.append(F[n](args[-1]) - 1)

assert expected == stefan
like image 74
Stefan Pochmann Avatar answered Nov 14 '22 21:11

Stefan Pochmann


Here is an example of what you want:

B = [10, 15, 20, 5]

F = [lambda x: x,
     lambda x: x * x,
     lambda x: x * 2 - 5]

def dostuff(i0, i1, i2, i3):
    print((i0, i1, i2, i3))

expected = []
for i0 in range(0, B[0]):
    for i1 in range(F[0](i0), B[1]):
        for i2 in range(F[1](i1), B[2]):
            for i3 in range(F[2](i2), B[3]):
                expected.append([i0, i1, i2, i3])

I found a recursive solution like this:

def iter_rec(found, fL, bL):
    if fL and bL:
        ik = found[-1] if found else 0
        fk = fL[0]
        bk = bL[0]
        for i in range(fk(ik), bk):
            for item in iter_rec(found + [i], fL[1:], bL[1:]):
                yield item
    else:
        yield found

# prepend the null function to ensure F and B have the same size
F = [lambda x: 0] + F

current = [item for item in iter_rec([], F, B)]

We have the same result.

assert expected == current
like image 20
Laurent LAPORTE Avatar answered Nov 14 '22 23:11

Laurent LAPORTE