views:

87

answers:

2

I guess it's an academic question, but the second result does not make sense to me. Shouldn't it be as thoroughly empty as the first? What is the rationale for this behavior?

from itertools import product

one_empty = [ [1,2], [] ]
all_empty = []

print [ t for t in product(*one_empty) ]  # []
print [ t for t in product(*all_empty) ]  # [()]

Updates

Thanks for all of the answers -- very informative.

Wikipedia's discussion of the Nullary Cartesian Product provides a definitive statement:

The Cartesian product of no sets ... is the singleton set containing the empty tuple.

And here is some code you can use to work through the insightful answer from sth:

from itertools import product

def tproduct(*xss):
    return ( sum(rs, ()) for rs in product(*xss) )

def tup(x):
    return (x,)

xs = [ [1, 2],     [3, 4, 5]       ]
ys = [ ['a', 'b'], ['c', 'd', 'e'] ]

txs = [ map(tup, x) for x in xs ]  # [[(1,), (2,)], [(3,), (4,), (5,)]]
tys = [ map(tup, y) for y in ys ]  # [[('a',), ('b',)], [('c',), ('d',), ('e',)]]

a = [ p for p in tproduct( *(txs + tys) )                   ]
b = [ p for p in tproduct( tproduct(*txs), tproduct(*tys) ) ]

assert a == b
+4  A: 
sth
In mathematics, there are many product and multiplication functions where there is no "neutral element". For a broad class with several examples, see http://en.wikipedia.org/wiki/Direct_product
Daniel Stutzbach
+1. Beautiful answer.You can make your product equality work with a slight modification: define a `flatten` function by: `flatten = lambda tups: sum(tups, ())`. Then `list(product(*(xs+ys)))` is equivalent to `map(flatten, product(product(*xs), product(*ys)))`. Moreover, the result of `itertools.product()` (with no args) is the correct one to make this equivalence continue to hold when either `xs` or `ys` (or both) is empty.
Mark Dickinson
Ah; now I see that that's pretty much exactly what you did, with your `tproduct` function. Sorry for the noise. :)
Mark Dickinson
@sth Thanks, I learned a lot from this.
FM
+2  A: 

As @sth already indicated, this behaviour is correct from a mathematical viewpoint. All you really need to convince yourself of is that list(itertools.product()) should have exactly one element, since once you know that it's clear what that element should be: it's got to be (for consistency) a tuple of length 0, and there's only one of those.

But the number of elements of itertools.product(l1, l2, l3, ...) should just be the product of the lengths of l1, l2, l3, ... . So the number of elements of itertools.product() should be the size of the empty product, and there's no shortage of internet sources that should persuade you that the empty product is 1.

I just wanted to point out that this is the correct practical definition as well as the correct mathematical one; that is, it's the definition that's most likely to 'just work' in boundary cases. For an example, suppose that you want to generate all strings of length n consisting of decimal digits, with the first digit nonzero. You might do something like:

import itertools

def decimal_strings(n):
    """Generate all digit strings of length n that don't start with 0."""
    for lead_digit in '123456789':
        for tail in itertools.product('0123456789', repeat=n-1):
            yield lead_digit + ''.join(tail)

What should this produce when n = 1? Well, in that case, you end up calling itertools.product with an empty product (repeat = 0). If it returned nothing, then the body of the inner for loop above would never be executed, so decimal_strings(1) would be an empty iterator; almost certainly not what you want. But since itertools.product('0123456789', repeat=0) returns a single tuple, you get the expected result:

>>> list(decimal_strings(1))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

(When n = 0, of course, this function correctly raises a ValueError.)

So in short, the definition is mathematically sound, and more often that not it's also what you want. It's definitely not a Python bug!

Mark Dickinson
+1 for the link to the Empty Product. It specifically mentions that for the Cartesian Product, the empty product is the singleton set containing the empty set.
Daniel Stutzbach
Great answer, both for the Empty Product reference and for the practical illustration.
FM