【问题标题】:How to get the cartesian product of multiple lists如何获得多个列表的笛卡尔积
【发布时间】:2023-02-19 03:47:11
【问题描述】:

如何从一组列表中获取笛卡尔积(所有可能的值组合)?

例如,给定

somelists = [
   [1, 2, 3],
   ['a', 'b'],
   [4, 5]
]

我怎么得到这个?

[(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), (2, 'a', 5), ...]

这种技术的一个常见应用是避免深度嵌套循环。有关更具体的副本,请参阅Avoiding nested for loops

如果你想要笛卡尔积相同多次列出自身,itertools.product 可以优雅地处理。见Operation on every pair of element in a listGenerating permutations with repetitions

许多已经知道 itertools.product 的人都在为每个输入序列期望单独的参数而不是例如列表的列表。接受的答案显示了如何使用 * 处理此问题。但是,这里使用* 解压参数是根本上没有什么不同从任何其他时间它在函数调用中使用。请参阅本主题的Expanding tuples into arguments(并酌情使用它来关闭重复的问题)。

【问题讨论】:

  • 请注意,“每个可能的组合”与“笛卡尔积”并不完全相同,因为在笛卡尔积中,允许重复。
  • 是否有笛卡尔积的非重复版本?
  • @KJW 是的,set(cartesian product)
  • 笛卡尔积中不应有重复项,除非输入列表本身包含重复项。如果您不希望笛卡尔积中出现重复项,请在所有输入列表中使用 set(inputlist)。不在结果上。
  • 在数学上,笛卡尔积是一个集合,所以笛卡尔积是不是包含重复项。另一方面,如果输入有重复,itertools.product 将在输出中有重复。所以 itertools.product 严格来说不是笛卡尔积,除非你将输入包装在 set 中,如@CamilB 所述。

标签: python list cartesian-product


【解决方案1】:

使用itertools.product,它从 Python 2.6 开始可用。

import itertools

somelists = [
   [1, 2, 3],
   ['a', 'b'],
   [4, 5]
]
for element in itertools.product(*somelists):
    print(element)

这与:

for element in itertools.product([1, 2, 3], ['a', 'b'], [4, 5]):
    print(element)

【讨论】:

  • 如果您使用 OP 提供的变量 somelists,则只想添加“*”字符。
  • @jaska:product()在结果中生成nitems_in_a_list ** nlists元素(reduce(mul, map(len, somelists)))。没有理由相信产生单个元素不是 O(nlists)(摊销),即时间复杂度与 simple nested for-loops 相同,例如,对于问题中的输入:nlists=3,元素总数结果:3*2*2,每个元素都有nlists项(在本例中为3)。
  • somelists 之前的* 有什么用?它有什么作用?
  • @VineetKumarDoshi:这里它用于将列表解包为函数调用的多个参数。在这里阅读更多:stackoverflow.com/questions/36901/…
  • 只是一个细节,但请注意 itertools.product() 也可以处理生成器,而不仅仅是类似列表的对象。
【解决方案2】:
import itertools
>>> for i in itertools.product([1,2,3],['a','b'],[4,5]):
...         print i
...
(1, 'a', 4)
(1, 'a', 5)
(1, 'b', 4)
(1, 'b', 5)
(2, 'a', 4)
(2, 'a', 5)
(2, 'b', 4)
(2, 'b', 5)
(3, 'a', 4)
(3, 'a', 5)
(3, 'b', 4)
(3, 'b', 5)
>>>

【讨论】:

    【解决方案3】:

    对于 Python 2.5 及更早版本:

    >>> [(a, b, c) for a in [1,2,3] for b in ['a','b'] for c in [4,5]]
    [(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), 
     (2, 'a', 5), (2, 'b', 4), (2, 'b', 5), (3, 'a', 4), (3, 'a', 5), 
     (3, 'b', 4), (3, 'b', 5)]
    

    这是 product() 的递归版本(只是一个例子):

    def product(*args):
        if not args:
            return iter(((),)) # yield tuple()
        return (items + (item,) 
                for items in product(*args[:-1]) for item in args[-1])
    

    例子:

    >>> list(product([1,2,3], ['a','b'], [4,5])) 
    [(1, 'a', 4), (1, 'a', 5), (1, 'b', 4), (1, 'b', 5), (2, 'a', 4), 
     (2, 'a', 5), (2, 'b', 4), (2, 'b', 5), (3, 'a', 4), (3, 'a', 5), 
     (3, 'b', 4), (3, 'b', 5)]
    >>> list(product([1,2,3]))
    [(1,), (2,), (3,)]
    >>> list(product([]))
    []
    >>> list(product())
    [()]
    

    【讨论】:

    • 如果某些 args 是迭代器,则递归版本不起作用。
    【解决方案4】:

    我会使用列表理解:

    somelists = [
       [1, 2, 3],
       ['a', 'b'],
       [4, 5]
    ]
    
    cart_prod = [(a,b,c) for a in somelists[0] for b in somelists[1] for c in somelists[2]]
    

    【讨论】:

    • @llekn 因为代码似乎固定为列表的数量
    • @Bằng Rikimaru 列表理解是如何固定的? lst = [i for i in itertools.product(*somelists)]
    • @LucasSchwartz 这个答案不使用 itertools,它使用链表理解循环。基本上,您的解决方案是另一个答案。
    【解决方案5】:

    itertools.product

    import itertools
    result = list(itertools.product(*somelists))
    

    【讨论】:

    • * 在 somelists 之前有什么用?
    • @VineetKumarDoshi“产品(somelists)”是子列表之间的笛卡尔积,Python 首先得到“[1, 2, 3]”作为一个元素,然后在下一个命令之后获取另一个元素,这就是换行符,所以第一个乘积项是 ([1, 2, 3],),第二个相似度 ([4, 5],) 等等“[([1, 2, 3],), ([4, 5],), ([6, 7],)]”.如果你想在元组内的元素之间获得笛卡尔积,你需要用 Asterisk 告诉 Python 关于元组结构。对于字典,您使用 **。更多here
    【解决方案6】:

    这是一个递归生成器,它不存储任何临时列表

    def product(ar_list):
        if not ar_list:
            yield ()
        else:
            for a in ar_list[0]:
                for prod in product(ar_list[1:]):
                    yield (a,)+prod
    
    print list(product([[1,2],[3,4],[5,6]]))
    

    输出:

    [(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]
    

    【讨论】:

    • 不过,它们存储在堆栈中。
    • @QuentinPradet 你是说像def f(): while True: yield 1 这样的生成器会在我们通过它时继续增加它的堆栈大小吗?
    • @QuentinPradet 是的,但即使在这种情况下也只需要最大深度的堆栈,而不是整个列表,所以在这种情况下堆栈为 3
    • 这是真的,对不起。基准可能很有趣。 :)
    【解决方案7】:

    在 Python 2.6 及更高版本中,您可以使用“itertools.product”。在旧版本的 Python 中,您可以使用以下(几乎 - 请参阅文档)等效的 code from the documentation,至少作为起点:

    def product(*args, **kwds):
        # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
        # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
        pools = map(tuple, args) * kwds.get('repeat', 1)
        result = [[]]
        for pool in pools:
            result = [x+[y] for x in result for y in pool]
        for prod in result:
            yield tuple(prod)
    

    两者的结果都是一个迭代器,所以如果你真的需要一个列表来进一步处理,请使用list(result)

    【讨论】:

    • 根据文档,实际的 itertools.product 实现不会构建中间结果,这可能很昂贵。对于中等大小的列表,使用此技术可能会很快失控。
    • 我只能将 OP 指向文档,而不是为他阅读。
    • 文档中的代码旨在演示产品功能的作用,而不是作为早期 Python 版本的解决方法。
    【解决方案8】:

    虽然已经有很多答案,但我想分享一些我的想法:

    迭代方法

    def cartesian_iterative(pools):
      result = [[]]
      for pool in pools:
        result = [x+[y] for x in result for y in pool]
      return result
    

    递归方法

    def cartesian_recursive(pools):
      if len(pools) > 2:
        pools[0] = product(pools[0], pools[1])
        del pools[1]
        return cartesian_recursive(pools)
      else:
        pools[0] = product(pools[0], pools[1])
        del pools[1]
        return pools
    def product(x, y):
      return [xx + [yy] if isinstance(xx, list) else [xx] + [yy] for xx in x for yy in y]
    

    拉姆达方法

    def cartesian_reduct(pools):
      return reduce(lambda x,y: product(x,y) , pools)
    

    【讨论】:

    • 在“迭代方法”中,为什么将结果声明为 result = [[]] 我知道它是 list_of_list 但通常即使我们声明了 list_of_list 我们也使用 [] 而不是 [[]]
    • 就 Pythonic 解决方案而言,我有点新手。您或路人能否在单独的循环中以“迭代方法”编写列表理解?
    • @SachinS 您在外部列表中使用了内部列表,因为您迭代了外部列表(结果为 x),而内部列表意味着外部列表不为空。如果它为空,则不会发生迭代,因为“结果”中没有 x。然后将项目添加到该列表中。该示例几乎取自官方文档,但我敢说它更含蓄而不是明确。如果你要将它重构为仅基于循环的代码并删除理解,就像 Johny Boy 所说的那样,那么它将需要更多的代码。
    • pools 是什么?它是我想要的产品列表的列表吗?
    • 有人可以帮忙解释一下这条线return [xx + [yy] if isinstance(xx, list) else [xx] + [yy] for xx in x for yy in y]
    【解决方案9】:

    递归方法:

    def rec_cart(start, array, partial, results):
      if len(partial) == len(array):
        results.append(partial)
        return 
    
      for element in array[start]:
        rec_cart(start+1, array, partial+[element], results)
    
    rec_res = []
    some_lists = [[1, 2, 3], ['a', 'b'], [4, 5]]  
    rec_cart(0, some_lists, [], rec_res)
    print(rec_res)
    

    迭代方法:

    def itr_cart(array):
      results = [[]]
      for i in range(len(array)):
        temp = []
        for res in results:
          for element in array[i]:
            temp.append(res+[element])
        results = temp
    
      return results
    
    some_lists = [[1, 2, 3], ['a', 'b'], [4, 5]]  
    itr_res = itr_cart(some_lists)
    print(itr_res)
    

    【讨论】:

      【解决方案10】:

      对上述可变参数递归生成器解决方案的一个小修改:

      def product_args(*args):
          if args:
              for a in args[0]:
                  for prod in product_args(*args[1:]) if args[1:] else ((),):
                      yield (a,) + prod
      

      当然还有一个包装器,它使它的工作方式与该解决方案完全相同:

      def product2(ar_list):
          """
          >>> list(product(()))
          [()]
          >>> list(product2(()))
          []
          """
          return product_args(*ar_list)
      

      一个权衡:它检查递归是否应该在每个外循环上中断,并且一次收获: 空调用时不产生任何结果,例如product(()),我认为这在语义上更正确(参见 doctest)。

      关于列表理解:数学定义适用于任意数量的参数,而列表理解只能处理已知数量的参数。

      【讨论】:

        【解决方案11】:

        只是对已经说过的内容补充一点:如果你使用 sympy,你可以使用符号而不是字符串,这使得它们在数学上很有用。

        import itertools
        import sympy
        
        x, y = sympy.symbols('x y')
        
        somelist = [[x,y], [1,2,3], [4,5]]
        somelist2 = [[1,2], [1,2,3], [4,5]]
        
        for element in itertools.product(*somelist):
          print element
        

        关于sympy

        【讨论】:

          【解决方案12】:

          列表理解简单明了:

          import itertools
          
          somelists = [
             [1, 2, 3],
             ['a', 'b'],
             [4, 5]
          ]
          lst = [i for i in itertools.product(*somelists)]
          

          【讨论】:

            【解决方案13】:

            我相信这有效:

            def cartesian_product(L):  
               if L:
                   return {(a,) + b for a in L[0] 
                                    for b in cartesian_product(L[1:])}
               else:
                   return {()}
            

            【讨论】:

              【解决方案14】:

              您可以使用标准库中的itertools.product 来获取笛卡尔积。 itertools 中其他很酷的相关实用程序包括 permutationscombinationscombinations_with_replacement。这是下面 sn-p 的 python 代码笔的 a link

              from itertools import product
              
              somelists = [
                 [1, 2, 3],
                 ['a', 'b'],
                 [4, 5]
              ]
              
              result = list(product(*somelists))
              print(result)
              

              【讨论】:

                【解决方案15】:

                这可以作为

                [(x, y) for x in range(10) for y in range(10)]
                

                另一个变量?没问题:

                [(x, y, z) for x in range(10) for y in range(10) for z in range(10)]
                

                【讨论】:

                  【解决方案16】:

                  在 99% 的情况下,您应该使用 itertools.product。它是用高效的 C 代码编写的,因此它可能比任何自定义实现都要好。

                  在 1% 的情况下你需要一个纯 Python 算法(例如,如果你需要以某种方式修改它),你可以使用下面的代码。

                  def product(*args, repeat=1):
                      """Find the Cartesian product of the arguments.
                  
                      The interface is identical to itertools.product.
                      """
                      # Initialize data structures and handle bad input
                      if len(args) == 0:
                          yield () # Match behavior of itertools.product
                          return
                      gears = [tuple(arg) for arg in args] * repeat
                      for gear in gears:
                          if len(gear) == 0:
                              return
                      tooth_numbers = [0] * len(gears)
                      result = [gear[0] for gear in gears]
                  
                      # Rotate through all gears
                      last_gear_number = len(gears) - 1
                      finished = False
                      while not finished:
                          yield tuple(result)
                  
                          # Get next result
                          gear_number = last_gear_number
                          while gear_number >= 0:
                              gear = gears[gear_number]
                              tooth_number = tooth_numbers[gear_number] + 1
                              if tooth_number < len(gear):
                                  # No gear change is necessary, so exit the loop
                                  result[gear_number] = gear[tooth_number]
                                  tooth_numbers[gear_number] = tooth_number
                                  break
                              result[gear_number] = gear[0]
                              tooth_numbers[gear_number] = 0
                              gear_number -= 1
                          else:
                              # We changed all the gears, so we are back at the beginning
                              finished = True
                  

                  界面与itertools.product 相同。例如:

                  >>> list(product((1, 2), "ab"))
                  [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
                  

                  与此页面上的其他纯 Python 解决方案相比,该算法具有以下优点:

                  • 它不会在内存中建立中间结果,从而保持较小的内存占用。
                  • 它使用迭代而不是递归,这意味着您不会收到“超出最大递归深度”错误。
                  • 它可以接受任意数量的输入迭代,使其比使用嵌套 for 循环更灵活。

                  此代码基于itertools.product algorithm from PyPy,即released under the MIT licence

                  【讨论】:

                    【解决方案17】:

                    以下代码是 Using numpy to build an array of all combinations of two arrays 的 95% 副本,所有学分都在那里!据说这要快得多,因为它只在 numpy 中。

                    import numpy as np
                    
                    def cartesian(arrays, dtype=None, out=None):
                        arrays = [np.asarray(x) for x in arrays]
                        if dtype is None:
                            dtype = arrays[0].dtype
                        n = np.prod([x.size for x in arrays])
                        if out is None:
                            out = np.zeros([n, len(arrays)], dtype=dtype)
                    
                        m = int(n / arrays[0].size) 
                        out[:,0] = np.repeat(arrays[0], m)
                        if arrays[1:]:
                            cartesian(arrays[1:], out=out[0:m, 1:])
                            for j in range(1, arrays[0].size):
                                out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
                        return out
                    

                    如果您不想从所有条目的第一个条目中获取数据类型,则需要将数据类型定义为参数。如果您有字母和数字作为项目,则采用 dtype = 'object'。测试:

                    somelists = [
                       [1, 2, 3],
                       ['a', 'b'],
                       [4, 5]
                    ]
                    
                    [tuple(x) for x in cartesian(somelists, 'object')]
                    

                    出去:

                    [(1, 'a', 4),
                     (1, 'a', 5),
                     (1, 'b', 4),
                     (1, 'b', 5),
                     (2, 'a', 4),
                     (2, 'a', 5),
                     (2, 'b', 4),
                     (2, 'b', 5),
                     (3, 'a', 4),
                     (3, 'a', 5),
                     (3, 'b', 4),
                     (3, 'b', 5)]
                    

                    【讨论】:

                      猜你喜欢
                      • 2015-07-29
                      • 2023-02-19
                      • 2021-03-05
                      • 2015-01-15
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-01-28
                      相关资源
                      最近更新 更多