13

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

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

更新

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

Wikipedia 对Nullary Cartesian Product的讨论提供了明确的声明:

无集合的笛卡尔积 ... 是包含空元组的单例集合。

以下是一些代码,您可以使用这些代码来解决 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

2 回答 2

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()而不需要为空输入引入特殊情况。

于 2010-07-01T01:49:03.367 回答
3

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

但元素的数量itertools.product(l1, l2, l3, ...)应该只是l1, l2, l3, ... 的长度的乘积。所以元素的个数itertools.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 错误!

于 2010-07-01T08:58:40.427 回答