当提供空列表时,itertools.product()会产生什么?

时间:2010-07-01 00:04:54

标签: python itertools cross-product

我想这是一个学术问题,但第二个结果对我来说没有意义。它不应该像第一个一样彻底空洞吗?这种行为的理由是什么?

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) ]  # [()]

更新

感谢所有答案 - 非常有用。

维基百科对Nullary Cartesian Product的讨论提供了一个明确的陈述:

  

没有套装的笛卡尔积......   是包含该单例的单例集   空元组。

以下是一些可用于处理富有洞察力的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

2 个答案:

答案 0 :(得分:10)

从数学的角度来看,没有元素的产品应该产生操作产品的中性元素,无论是什么。

例如,对于整数,乘法的中性元素是 1 ,因为对于所有整数 a 1⋅a= a 。因此,整数的空乘积应为 1 。当实现返回数字列表的乘积的python函数时,这自然会发生:

def iproduct(lst):
  result = 1
  for i in lst:
    result *= i
  return result

要使用此算法计算正确的结果,需要使用result初始化1。当在空列表上调用函数时,这会导致返回值1

对于函数而言,此返回值也非常合理。如果您首先连接两个列表然后构建元素的产品,或者如果您首先构建两个单独列表的产品然后将结果相乘,那么,如果您具有良好的产品功能,则无关紧要:

iproduct(xs + ys) == iproduct(xs) * iproduct(ys)

如果xsys为空,则仅在iproduct([]) == 1时有效。

现在迭代器上的product()更加复杂。在这里,从数学的角度来看,product([])应该返回该操作的中性元素,无论是什么。自[]起不是product([], xs) == [],而中性元素product([], xs) == xs应该成立[()]。但事实证明,>>> list(product([()], [1,2,3])) [((), 1), ((), 2), ((), 3)] 也不是中性元素:

product()

事实上,product(*(xs + ys)) != product(product(*xs), product(*ys)) 根本不是一个非常好的数学产品,因为上面的等式并不成立:

[()]

产品的每个应用程序都会生成一个额外的元组层,并且无法解决这个问题,因此甚至不可能存在真正的中性元素。 [()]非常接近,它不添加或删除任何元素,只是为每个元素添加一个空元组。

def tproduct(*xss): # the parameters have to be lists of tuples return (sum(rs, ()) for rs in product(*xss)) 实际上是这个略微适应的产品函数的中性元素,它仅在元组列表上运行,但不会在每个应用程序上添加额外的元组层:

def tup(x): return (x,)
txs = [map(tup, x) for x in xs]
tys = [map(tup, y) for y in ys]
tproduct(*(txs + tys)) == tproduct(tproduct(*txs), tproduct(*tys))

对于此功能,上述乘积等式成立:

tproduct()

通过将输入列表打包到元组中的附加预处理步骤,product()给出与[()]相同的结果,但从数学角度来看表现更好。它的中性元素也是[()]

所以product()作为这种列表乘法的中性元素是有意义的。即使它不完全适合tproduct(),它也是这个函数的一个很好的选择,因为它允许定义{{1}},而不需要为空输入引入特殊情况。

答案 1 :(得分:3)

正如@sth已经指出的那样,从数学角度来看,这种行为是正确的。所有你真正需要说服自己的是list(itertools.product())应该只有一个元素,因为一旦你知道它应该是什么元素应该是什么:它必须是(为了一致性)一个长度为0的元组,并且有只有其中一个。

itertools.product(l1, l2, l3, ...)的元素数量应该只是l1l2l3,...长度的乘积。因此,itertools.product()的元素数量应该是empty product的大小,并且不缺少可以说服空产品为1的互联网资源。

我只想指出这是正确的实用定义以及正确的数学定义;也就是说,它是最有可能在边界情况下“正常工作”的定义。例如,假设您要生成长度为n且由十进制数字组成的所有字符串,第一个数字非零。您可以执行以下操作:

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)

n = 1时应该产生什么?那么,在这种情况下,您最终会使用空产品(itertools.product)调用repeat = 0。如果它什么都没有返回,那么上面内部for循环的主体永远不会被执行,所以decimal_strings(1)将是一个空的迭代器;几乎肯定不是你想要的。但是由于itertools.product('0123456789', repeat=0)返回单个元组,因此您得到了预期的结果:

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

(当n = 0时,这个函数正确地引发了一个ValueError。)

简而言之,这个定义在数学上是合理的,更常见的是它并不是你想要的。这绝对不是Python的错误!