【问题标题】:What should itertools.product() yield when supplied an empty list?当提供一个空列表时, itertools.product() 应该产生什么?
【发布时间】:2010-07-01 00:04:54
【问题描述】:

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

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

【问题讨论】:

    标签: python itertools cross-product


    【解决方案1】:

    从数学的角度来看,任何元素的乘积都应该产生操作 product 的中性元素,不管是什么。

    例如对于整数,乘法的中性元素是1,因为对于所有整数a1 ⋅ 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(),而无需为空输入引入特殊情况。

    【讨论】:

    • 在数学中,有很多乘积和乘法函数没有“中性元素”。有关包含多个示例的广泛类,请参阅en.wikipedia.org/wiki/Direct_product
    • +1。美丽的答案。您可以通过稍微修改使您的产品相等性工作:定义 flatten 函数:flatten = lambda tups: sum(tups, ())。那么list(product(*(xs+ys))) 等价于map(flatten, product(product(*xs), product(*ys)))。此外,当xsys(或两者都为空)为空时,itertools.product()(没有参数)的结果是使这种等价继续成立的正确结果。
    • 啊;现在我看到这几乎就是你所做的,你的tproduct 函数。对不起,噪音。 :)
    • 因此,如果您尝试对可能具有空子列表元素的列表进行乘积,那么 TL,DR 就是确保子列表至少有一个空的字符串或其他空的可迭代对象。例如,如果使用 dict 中的字符串,请执行以下操作:mappings.get(char, [""])
    【解决方案2】:

    正如@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 时会产生什么?好吧,在这种情况下,您最终会用一个空产品 (repeat = 0) 调用 itertools.product。如果它什么都不返回,那么上面的内部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 错误!

    【讨论】:

    • +1 用于指向空产品的链接。它特别提到对于笛卡尔积,空积是包含空集的单例集。
    • 很好的答案,对于空产品参考和实际插图。
    猜你喜欢
    • 1970-01-01
    • 2012-10-06
    • 1970-01-01
    • 2015-05-26
    • 2019-02-22
    • 2016-05-09
    • 2020-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多