如何从一组列表中得到笛卡尔积(每一种可能的值组合)?

输入:

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), ...]

该技术的一个常见应用是避免深度嵌套循环。有关更具体的重复,请参见避免嵌套for循环。

如果你想要一个相同列表与它自身多次相乘的笛卡尔积,itertools。Product可以很好地处理这个问题。参见对列表中的每对元素的操作或生成具有重复的排列。


当前回答

递归的方法:

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)

其他回答

在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。PyPy的产品算法,它是在MIT许可下发布的。

你可以使用itertools。用标准库中的积来得到笛卡尔积。itertools中其他很酷的相关实用程序包括排列、组合和combinations_with_replacement。下面是一个python代码片段的链接:

from itertools import product

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

result = list(product(*somelists))
print(result)

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

迭代方法

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]

Lambda方法

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

列表推导式简单明了:

import itertools

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

只是补充一点已经说过的:如果你使用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。