我想这是一个学术问题,但第二个结果对我来说没有意义。它不应该像第一个一样彻底空洞吗?这种行为的理由是什么?
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
答案 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)
如果xs
或ys
为空,则仅在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, ...)
的元素数量应该只是l1
,l2
,l3
,...长度的乘积。因此,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的错误!