Welcome, guest | Sign In | My Account | Store | Cart
2

Python has a number of nice methods to handle 'for' loops. However, the situation often arises where you have a large number of nested loops. Using this solution reduces the number of loops to one.

Python, 61 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from copy import copy

def mrange(min_values, max_values=None):
    '''
        Inputs: min_values, a list/tuple with the starting values
                    if not given, assumed to be zero
                max_values: a list/tuple with the ending values
        outputs: a tuple of values
    '''
    
    if not max_values:
        max_values = min_values
        min_values = [0 for i in max_values]
    indices_list = copy(min_values)

    #Yield the (0,0, ..,0) value
    yield tuple(indices_list)
    
    while(True):
        indices_list = updateIndices(indices_list, min_values, max_values)
        if indices_list:
            yield tuple(indices_list)
        else:
            break#We're back at the beginning
   
def updateIndices(indices_list, min_values, max_values):
    '''
        Update the list of indices
    '''
    for index in xrange(len(indices_list)-1, -1, -1):
        #If the indices equals the max values, the reset it and 
        #move onto the next value
        if not indices_list[index] == max_values[index] - 1:
            indices_list[index] += 1
            return indices_list
        else:
            indices_list[index] = min_values[index]
    return False        

#example

for i in mrange([2,2,1,2]):
    print i
(0, 0, 0, 0)
(0, 0, 0, 1)
(0, 1, 0, 0)
(0, 1, 0, 1)
(1, 0, 0, 0)
(1, 0, 0, 1)
(1, 1, 0, 0)
(1, 1, 0, 1)

#So the rather horrid
for i in range(1):
    for j in range(2):
        for k in range(3):
            for l in range(4):
                print i, j, k, l
#reduces to
for i, j, k, l in mrange([1,2,3,4]):
    print i, j, k, l

This recipe can be easily extend to iterate over object, floats and anything else you could think of.

2 comments

Steven Bethard 7 years, 8 months ago  # | flag

This seems like a rather complicated way of saying::

>>> def multi_for(iterables):
...     if not iterables:
...         yield ()
...     else:
...         for item in iterables[0]:
...             for rest_tuple in multi_for(iterables[1:]):
...                 yield (item,) + rest_tuple
...
>>> for i in multi_for(map(xrange, [2, 2, 1, 2])):
...     print i
...
(0, 0, 0, 0)
(0, 0, 0, 1)
(0, 1, 0, 0)
(0, 1, 0, 1)
(1, 0, 0, 0)
(1, 0, 0, 1)
(1, 1, 0, 0)
(1, 1, 0, 1)

Note that this version can handle any kind of iterables, not just ranges. If you want to iterate over ranges, simply pass in range or xrange objects (as shown above). If repeatedly slicing the iterables list and creating the intermediate tuples freaks you out, you could write this instead like:

>>> def multi_for(iterables):
...     end = len(iterables)
...     def helper(index):
...         if index >= end:
...             yield []
...         else:
...             for item in iterables[index]:
...                 for rest_list in helper(index + 1):
...                     rest_list.append(item)
...                     yield rest_list
...     for reversed_items in helper(0):
...         yield tuple(reversed(reversed_items))
...
>>> for i in multi_for(map(xrange, [2, 2, 1, 2])):
...     print i
...
(0, 0, 0, 0)
(0, 0, 0, 1)
(0, 1, 0, 0)
(0, 1, 0, 1)
(1, 0, 0, 0)
(1, 0, 0, 1)
(1, 1, 0, 0)
(1, 1, 0, 1)
Raymond Hettinger 7 years, 8 months ago  # | flag

Tweaked for Speed. Nice recipe, Colin.

FWIW, here's an optimized version:

def mrange(minvec, maxvec=None):
    if maxvec is None:
        maxvec = minvec
        minvec = [0] * len(maxvec)
    vec = list(minvec)
    unitpos = len(vec) - 1
    maxunit = maxvec[unitpos]
    _tuple = tuple
    while 1:
        if vec[unitpos] == maxunit:
            i = unitpos
            while vec[i] == maxvec[i]:
                vec[i] = minvec[i]
                i -= 1
                if i == -1:
                    return
                vec[i] += 1
        yield _tuple(vec)
        vec[unitpos] += 1

Add a comment

Sign in to comment