1

I'm on a Python project that uses this for-loop all over the place

for year in yearsindex:
    for month in monthindex:
        for hour in hourindex:
            for node in nodeindex:
                dosomething(year, month, hour, node)

I was wondering if there was a way to combine all of the iterators into a single iterator to be more readable

Something in the form of

for (year, month, hour, node) in combinediterator:
    dosomething(year, month, hour, node)
Grant
  • 43
  • 6

2 Answers2

4

This is itertools.product:

import itertools
for year, month, hour, node in itertools.product(
        yearsindex, monthindex, hourindex, nodeindex):
    dosomething(year, month, hour, node)

You can see that cramming all that onto a single logical line isn't really a readability improvement. There are several ways to make it an improvement. For example, if you can avoid unpacking the tuples the iterator gives you, or if you can put the arguments to itertools.product in a list and unpack them with *args:

for arg_tuple in itertools.product(*indexes):
    dosomething(*arg_tuple)

If the loop body is longer than just one line of dosomething, you also get the benefit of decreased indentation. With a short loop body, that doesn't matter much.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • great answer although imho this is not at all more readable than the other method – Joran Beasley Mar 28 '16 at 18:00
  • @JoranBeasley: Yeah, cramming all that onto a line isn't really better than the nested loops. I've added some more text on how to make it prettier. – user2357112 Mar 28 '16 at 18:13
  • My initial reaction was to do this with a generator. IMO this is the better solution. – Deacon Mar 28 '16 at 18:22
  • is there any chance you could clarify how the (`*`indexes) works? I've never seen the '`*`' notation before. What does it do? – Grant Mar 28 '16 at 18:29
  • @Grant: It's argument unpacking. There's a [previous question](http://stackoverflow.com/questions/36901/what-does-double-star-and-star-do-for-python-parameters) on the topic. – user2357112 Mar 28 '16 at 18:31
1

Why don't you just wrap it in a function definition in a form of a generator, like so:

>>> l1 = [1,2,3]
>>> l2 = [4,5,6]
>>> l3 = [7,8,9]
>>>
>>> 
>>> def comb_gen(a,b,c):
        for x in a:
            for y in b:
                for z in c:
                    yield (x,y,z)


>>> 
>>> for x,y,z in comb_gen(l1,l2,l3):
        print(x,y,z)


1 4 7
1 4 8
1 4 9
1 5 7
1 5 8
1 5 9
1 6 7
1 6 8
1 6 9
2 4 7
2 4 8
2 4 9
2 5 7
2 5 8
2 5 9
2 6 7
2 6 8
2 6 9
3 4 7
3 4 8
3 4 9
3 5 7
3 5 8
3 5 9
3 6 7
3 6 8
3 6 9
Iron Fist
  • 10,739
  • 2
  • 18
  • 34
  • great answer as this will allow him to maintain the understandability of what the operation is doing as well – Joran Beasley Mar 28 '16 at 18:00
  • @JoranBeasley ..Thanks for support..although I was a bit worried to post it as answer as I was thinking I might be re-inventing the wheel with `itertools.product` ... I hope I did not – Iron Fist Mar 28 '16 at 18:02
  • 2
    Unfortunately, you did. I think this just looks more readable because of the shortened variable names you used. – user2357112 Mar 28 '16 at 18:15