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, m):
ranges = [ range (m) ] * n
yield from product (*ranges)
for prod in foo (4, 2):
print (prod)
# (0, 0, 0, 0)
# (0, 0, 0, 1)
# (0, 0, 1, 0)
# (0, 0, 1, 1)
# (0, 1, 0, 0)
# (0, 1, 0, 1)
# (0, 1, 1, 0)
# (0, 1, 1, 1)
# (1, 0, 0, 0)
# (1, 0, 0, 1)
# (1, 0, 1, 0)
# (1, 0, 1, 1)
# (1, 1, 0, 0)
# (1, 1, 0, 1)
# (1, 1, 1, 0)
# (1, 1, 1, 1)
Or use destructuring assignment to create bindings for individual elements of the product. In your program, simply replace print
with your real function -
for (w, x, y, z) in foo (4, 2):
print ("w", w, "x", x, "y", y, "z", z)
# w 0 x 0 y 0 z 0
# w 0 x 0 y 0 z 1
# w 0 x 0 y 1 z 0
# w 0 x 0 y 1 z 1
# w 0 x 1 y 0 z 0
# w 0 x 1 y 0 z 1
# w 0 x 1 y 1 z 0
# w 0 x 1 y 1 z 1
# w 1 x 0 y 0 z 0
# w 1 x 0 y 0 z 1
# w 1 x 0 y 1 z 0
# w 1 x 0 y 1 z 1
# w 1 x 1 y 0 z 0
# w 1 x 1 y 0 z 1
# w 1 x 1 y 1 z 0
# w 1 x 1 y 1 z 1
Because product
is defined as a generator, we are afforded much flexibility even when writing more complex programs. Consider this program that finds right triangles made up whole numbers, a Pythagorean triple. Also note that product
allows you to repeat an iterable as input as see in product (r, r, r)
below
def is_triple (a, b, c):
return a * a + b * b == c * c
def solver (n):
r = range (1, n)
for p in product (r, r, r):
if is_triple (*p):
yield p
print (list (solver (20)))
# (3, 4, 5)
# (4, 3, 5)
# (5, 12, 13)
# (6, 8, 10)
# (8, 6, 10)
# (8, 15, 17)
# (9, 12, 15)
# (12, 5, 13)
# (12, 9, 15)
# (15, 8, 17)
For additional explanation and a way to see how to do this without using generators, view this answer.