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

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. Steven Bethard 16 years, 10 months ago

This seems like a rather complicated way of saying::

``````>>> def multi_for(iterables):
...     if not iterables:
...         yield ()
...     else:
...         for item in iterables:
...             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 16 years, 10 months ago

Tweaked for Speed. Nice recipe, Colin.

FWIW, here's an optimized version:

``````def mrange(minvec, maxvec=None):
if maxvec is None:
maxvec = minvec
minvec =  * 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
`````` Created by Colin Gillespie on Tue, 30 Jan 2007 (PSF)