如何从一组列表中得到笛卡尔积(每一种可能的值组合)?
输入:
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可以很好地处理这个问题。参见对列表中的每对元素的操作或生成具有重复的排列。
在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许可下发布的。
虽然已经有很多答案,但我想分享一些我的想法:
迭代方法
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)
出现使用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)
这是一个递归生成器,它不存储任何临时列表
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)]
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)
>>>