Someone recently asked a very similar question: "[How to] determine all combinations of flipping a coin without using itertools.product
?" to which I provided an answer for. I wouldn't consider it a duplicate because I believe itertools.product
is the best fit for your particular problem.
You may however be wondering how itertools.product
does the trick. Or maybe you want to encode a different transformation in your recursive expansion. Below, I'll share one possible solution using Python's generators
def product (*iters):
def loop (prod, first = [], *rest):
if not rest:
for x in first:
yield prod + (x,)
else:
for x in first:
yield from loop (prod + (x,), *rest)
yield from loop ((), *iters)
for prod in product ("ab", "xyz"):
print (prod)
# ('a', 'x')
# ('a', 'y')
# ('a', 'z')
# ('b', 'x')
# ('b', 'y')
# ('b', 'z')
Because product
accepts a a list of iterables, any iterable input can be used in the product. They can even be mixed as demonstrated here
print (list (product (['@', '%'], range (2), "xy")))
# [ ('@', 0, 'x')
# , ('@', 0, 'y')
# , ('@', 1, 'x')
# , ('@', 1, 'y')
# , ('%', 0, 'x')
# , ('%', 0, 'y')
# , ('%', 1, 'x')
# , ('%', 1, 'y')
# ]
We could make a program foo
that provides the output posted in your question
def foo (n):
def build_ranges (m):
if m == 0:
return []
else:
return [ range (m) ] + build_ranges (m - 1)
yield from product (*build_ranges (n))
for prod in foo (3):
print (prod)
# (0, 0, 0)
# (0, 1, 0)
# (1, 0, 0)
# (1, 1, 0)
# (2, 0, 0)
# (2, 1, 0)
Or use destructuring assignment to create bindings for individual elements of the product
for (x,y,z) in foo (3):
print ("x", x, "y", y, "z", z)
# x 0 y 0 z 0
# x 0 y 1 z 0
# x 1 y 0 z 0
# x 1 y 1 z 0
# x 2 y 0 z 0
# x 2 y 1 z 0
Other qualities of this product
implementation are discussed in my answer to the question I linked. If you're interested to see how to do this without generators, I provide a purely functional solution to the problem as well.