在一个列表中对每一对元素进行操作

130

使用Python,我想要比较列表中每一个可能的组合。

假设我有以下列表:

my_list = [1,2,3,4]

我想对列表中任意两个元素进行操作(我们称之为foo)。

最终结果应该与以下内容相同:

foo(1,1)
foo(1,2)
...
foo(4,3)
foo(4,4)

我的第一个想法是手动两次迭代列表,但那似乎不够Pythonic。


请参考 https://dev59.com/GHA75IYBdhLWcg3wy8br,了解将结果存储在新列表中的情况。 - Karl Knechtel
5个回答

294

请查看itertools模块中的product()函数,它可以完全实现您所描述的功能。

import itertools

my_list = [1,2,3,4]
for pair in itertools.product(my_list, repeat=2):
    foo(*pair)

这等同于:

my_list = [1,2,3,4]
for x in my_list:
    for y in my_list:
        foo(x, y)

编辑: 还有两个非常相似的函数,permutations()combinations()。为了说明它们的区别:

product() 会生成所有可能的元素配对,包括所有重复项:

1,1  1,2  1,3  1,4
2,1  2,2  2,3  2,4
3,1  3,2  3,3  3,4
4,1  4,2  4,3  4,4

permutations() 生成由每对不同元素的独特排列组成,消除了 x,x 的重复:

 .   1,2  1,3  1,4
2,1   .   2,3  2,4
3,1  3,2   .   3,4
4,1  4,2  4,3   .

最后,combinations() 仅以词典顺序生成每个唯一的元素对:

 .   1,2  1,3  1,4
 .    .   2,3  2,4
 .    .    .   3,4
 .    .    .    .

这三个函数都是在Python 2.6中引入的。


1
当我运行itertools.product(my_list, 2)时,它会抱怨int不可调用。一旦我将其更改为:itertools.product(my_list, repeat=2),它就可以正常工作了。 - ojrac
请注意,itertools.product() 是 Python 2.6 中的新功能。 - Mike Mazur
4
还有一个函数叫做combinations_with_replacement()。它类似于combinations(),但包括对角线(与图示保持一致)。 - Ziegl
2
懒人福利:要使用permutations()combinations()得到上述结果,可以使用r=2代替在product()示例中使用的repeat=2 - Rob
有没有一种方法可以使用itertools获取所有组合以及所有(1, 1), (2, 2), (3, 3)... - nbro
显示剩余3条评论

22

我曾遇到相似的问题,并在这里找到了解决方案。它可以在不导入任何模块的情况下工作。

假设有一个像这样的列表:

people = ["Lisa","Pam","Phil","John"]
一个简化的一行解决方案如下。 所有可能的配对,包括重复:
result = [foo(p1, p2) for p1 in people for p2 in people]

所有可能的配对,不包括重复项:

result = [foo(p1, p2) for p1 in people for p2 in people if p1 != p2]

唯一的配对,其中顺序无关紧要:

result = [foo(people[p1], people[p2]) for p1 in range(len(people)) for p2 in range(p1+1,len(people))]

如果你只想获取配对结果而不需要执行操作,那么删除函数foo并仅使用元组即可。

包括重复的所有可能的配对:

list_of_pairs = [(p1, p2) for p1 in people for p2 in people]

结果:

('Lisa', 'Lisa')
('Lisa', 'Pam')
('Lisa', 'Phil')
('Lisa', 'John')
('Pam', 'Lisa')
('Pam', 'Pam')
('Pam', 'Phil')
('Pam', 'John')
('Phil', 'Lisa')
('Phil', 'Pam')
('Phil', 'Phil')
('Phil', 'John')
('John', 'Lisa')
('John', 'Pam')
('John', 'Phil')
('John', 'John')

所有可能的配对,不包括重复的:

list_of_pairs = [(p1, p2) for p1 in people for p2 in people if p1 != p2]

结果:

('Lisa', 'Pam')
('Lisa', 'Phil')
('Lisa', 'John')
('Pam', 'Lisa')
('Pam', 'Phil')
('Pam', 'John')
('Phil', 'Lisa')
('Phil', 'Pam')
('Phil', 'John')
('John', 'Lisa')
('John', 'Pam')
('John', 'Phil')

唯一的一对,其中顺序无关:

list_of_pairs = [(people[p1], people[p2]) for p1 in range(len(people)) for p2 in range(p1+1,len(people))]

结果:

('Lisa', 'Pam')
('Lisa', 'Phil')
('Lisa', 'John')
('Pam', 'Phil')
('Pam', 'John')
('Phil', 'John')

编辑:在简化这个解决方案后,我意识到这与Adam Rosenfield的方法相同。我希望更详细的解释有助于一些人更好地理解。


2
我非常喜欢这种方式,比导入一个库要干净得多! - sudo-nim
3
itertools 是 Python 的一部分,而不是外部库。 - GuiSim

4

如果你只是在调用一个函数,那么你不能做得比以下代码更好:

for i in my_list:
    for j in my_list:
        foo(i, j)

如果您想收集调用函数的结果列表,可以这样做:
[foo(i, j) for i in my_list for j in my_list]

这将返回一个列表,其中包含对每个可能的对 (i, j) 应用 foo(i, j) 的结果。


0

Ben Bank的答案适用于希望按字典顺序排序的组合。但是,如果您希望组合随机排序,这里有一个解决方案:

import random
from math import comb


def cgen(i,n,k):
    """
    returns the i-th combination of k numbers chosen from 0,1,...,n-1
    
    forked from: https://math.stackexchange.com/a/1227692
    changed from 1-indexed to 0-indexed.
    """
    # 1-index
    i += 1
    
    c = []
    r = i+0
    j = 0
    for s in range(1,k+1):
        cs = j+1
        while r-comb(n-cs,k-s)>0:
            r -= comb(n-cs,k-s)
            cs += 1
        c.append(cs-1)
        j = cs
    return c


def generate_random_combinations(n, k, shuffle=random.shuffle):
    """
    Generate combinations in random order of k numbers chosen from 0,1,...,n-1.
    
    :param shuffle: Function to in-place shuffle the indices of the combinations. Use for seeding.
    """
    total_combinations = comb(n, k)
    combination_indices = list(range(total_combinations))
    shuffle(combination_indices)
    
    for i in combination_indices:
        yield cgen(i, n, k)

示例用法

对于 N=100k=4

gen_combos = generate_random_combinations(100, 4)

for i in range(3):
    print(next(gen_combos))

结果为:

[4, 9, 55, 79]
[11, 49, 58, 64]
[75, 82, 83, 91]

使用案例

对于我的使用案例,我正在实现一个算法,它搜索单个(或少数)组合,并在找到有效组合时停止。平均而言,它只遍历了所有可能组合的非常小的子集,因此没有必要提前构建所有可能的组合,然后进行洗牌操作(总体规模太大,无法将所有组合都存储在内存中)。

随机性对于快速找到解决方案至关重要,因为按字典顺序排序会导致人口中的单个值被包含在所有组合中,直到它耗尽。例如,如果 n=100k=4,则结果如下:

索引 组合
0 (0, 1, 2, 3)
1 (0, 1, 2, 4)
2 (0, 1, 2, 5)
...
156848 (0, 97, 98, 99)
156849 (1, 2, 3, 4)

如果0不是有效解的一部分,那么我们将无缘无故地搜索了156849个组合。随机排序可以帮助减轻这个问题(请参见上面的示例输出)。


0
my_list = [1,2,3,4]

pairs=[[x,y] for x in my_list for y in my_list]
print (pairs)

尽管这段代码可能解决了问题,但一个好的答案还需要解释代码是做什么的以及它是如何解决问题的。 - BDL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接