如何将Python的itertools.product从列表推导转换为普通的for循环?

1 投票
3 回答
1019 浏览
提问于 2025-04-17 14:15

根据这个链接,下面这个函数的功能和他们的库是一样的(我把一些不需要的部分去掉了):

def product(*args):
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
    pools = map(tuple, args)
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)

在我的例子中,我传递给这个产品函数的是3个列表,但我需要添加一些条件检查,这样就不会把某些列表中的项目和其他列表中的项目混在一起,除非它们符合要求。所以我想我需要把:

result = [x+[y] for x in result for y in pool]

转换成“普通的”FOR循环(我不太确定该怎么称呼它们),这样我就可以添加几个IF检查,来确认列表中的项目是否应该混合在一起。

让我感到困惑的是,“x”是在遍历一个空的“result”列表,但在遍历的过程中又往里面添加了项目,所以我觉得这让转换成普通循环变得复杂了。

这是我尝试过的其中一个例子:

def product(*args):
    pools = map(tuple, args)
    result = [[]]
    for pool in pools:
        for x in result:
            for y in pool:
                result.append(x+[y])
    for prod in result:
        yield tuple(prod)

任何帮助都非常感谢!

3 个回答

2

注意,在这一行 result = [x+[y] for x in result for y in pool] 中,result 出现了两次,但这并不重要。这个表达式是用旧的 result 来构建一个新的列表,然后把这个新列表赋值给 result

这可能让你感到困惑。一个等价的详细版本可以是:

def product(*args):
    pools = map(tuple, args)
    result = [[]]
    for pool in pools:
        tmp = []
        for x in result:   # note that it's the old 'result' here
            for y in pool:
                tmp.append(x+[y])
        result = tmp
    for prod in result:
        yield tuple(prod)
3

这个产品函数其实是在把多个列表进行相乘,类似于一种归约操作。如果你想在这个过程中筛选结果,这样的做法可能对你没什么帮助。相反,你应该写一个产品函数,它可以接收固定数量的列表:

for x in list1:
    for y in list2:
        for z in list3:
            if condition(x, y, z):
                yield tuple(x, y, z)
4

你已经很接近了:嵌套列表推导式的右边部分是按照你写循环的顺序来写的,所以这一点你做得对。不过,在列表推导式的版本中,首先会计算赋值的右边部分,然后再把结果绑定到左边的变量上。所以

result = [x+[y] for x in result for y in pool]

需要变成

new_result = []
for x in result:
    for y in pool:
        new_result.append(x+[y])
result = new_result

这样你在遍历的时候就不会修改到 result 了。如果你想禁止某些排列方式——而且你可以把你的约束条件写得适合那种从左到右的遍历顺序——那么你可以这样做:

def filtered_product(args, filter_fn):
    pools = map(tuple, args)
    result = [[]]
    for pool in pools:
        new_result = []
        for x in result:
            for y in pool:
                new_val = x+[y]
                if filter_fn(new_val):
                    new_result.append(x+[y])
        result = new_result
        print 'intermediate result:', result
    for prod in result:
        yield tuple(prod)

这样就会得到

In [25]: list(filtered_product([[1,2,3], [4,5,6], [7,8,9]], lambda x: sum(x) % 3 != 2))
intermediate result: [[1], [3]]
intermediate result: [[1, 5], [1, 6], [3, 4], [3, 6]]
intermediate result: [[1, 5, 7], [1, 5, 9], [1, 6, 8], [1, 6, 9], [3, 4, 8], [3, 4, 9], [3, 6, 7], [3, 6, 9]]
Out[25]: 
[(1, 5, 7),
 (1, 5, 9),
 (1, 6, 8),
 (1, 6, 9),
 (3, 4, 8),
 (3, 4, 9),
 (3, 6, 7),
 (3, 6, 9)]

这是否比直接使用 (p for p in itertools.product(whatever) if condition(p)) 更有好处,取决于你能剪掉多少分支,因为正如你所看到的,它会在内存中构建所有的中间列表。

撰写回答