The simplest recursive definition of the cartesian product I can think of looks like this. You can see that like yours, this has a loop -- actually, two loops embedded in a list comprehension. Unlike yours, this can handle two or more sequences:
def product(*seqs):
if not seqs:
return [[]]
else:
return [[x] + p for x in seqs[0] for p in product(*seqs[1:])]
Here's a walk-through to give you a sense of how it works. By definition, the cartesian product of an empty sequence (product()
) is a sequence containing the empty sequence. In other words, product()
== [[]]
-- see here for why.
Now let's say we call product([1, 2, 3])
-- seqs
is non-empty, so the return value is the result of the list comprehension. In this case, product(*seqs[1:])
== product(*[])
== product()
== [[]]
, so the list comprehension is equivalent to this:
[[x] + p for x in seqs[0] for p in [[]] ]
Which is equivalent to all of these:
[[x] + [] for x in seqs[0]]
[[x] for x in seqs[0]]
[[1], [2], [3]]
Adding another sequence, we have product([4, 5, 6], [1, 2, 3])
. Now the list comprehension looks like this:
[[x] + p for x in [4, 5, 6] for p in product(*seqs[1:])]
[[x] + p for x in [4, 5, 6] for p in [[1], [2], [3]]]
[[4, 1], [4, 2], [4, 3], [5, 1], [5, 2], [5, 3], [6, 1], [6, 2], [6, 3]]
The pattern continues from there; for each additional sequence, each of the values in the sequence is prepended to each of the values in the cartesian product of the following sequences.