【问题标题】:Operation on every pair of element in a list对列表中每一对元素的操作
【发布时间】:2009-06-03 00:21:23
【问题描述】:

使用 Python,我想比较列表中每个可能的对。

假设我有

my_list = [1,2,3,4]

我想对列表中 2 个元素的每个组合进行一次操作(我们称之为 foo)。

最终结果应该是一样的

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

我的第一个想法是手动遍历列表两次,但这似乎不是很pythonic。

【问题讨论】:

    标签: python


    【解决方案1】:

    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 中引入的。

    【讨论】:

    • 奇怪,当我运行 itertools.product(my_list, 2) 时,它抱怨 int 不可调用。一旦我将其更改为:itertools.product(my_list, repeat=2)
    • 请注意,itertools.product() 是 Python 2.6 中的新功能。
    • 为了后代,我会指出 itertools.combinations 不会在原始示例中生成 foo(1,1) 或 foo(4,4) 行。
    • 还有combinations_with_replacement()。类似于 combine(),但包括对角线(与插图一致)。
    • 对于懒人:要使用permutations()combinations() 获得上述结果,请使用r=2 代替repeat=2 示例中用于product()product()
    【解决方案2】:

    我遇到了类似的问题并找到了解决方案here。它无需导入任何模块即可工作。

    假设一个列表如下:

    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 的方法相同。我希望更大的解释能帮助一些人更好地理解它。

    【讨论】:

    • 比起导入库,我更喜欢这个,更干净!
    • itertools 是 Python 的一部分。它不是外部库。
    【解决方案3】:

    如果你只是调用一个函数,你真的不能做得更好:

    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]
    

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

    【讨论】:

      【解决方案4】:
      my_list = [1,2,3,4]
      
      pairs=[[x,y] for x in my_list for y in my_list]
      print (pairs)
      

      【讨论】:

      • 虽然这段代码可能会解决问题,但一个好的答案还需要解释代码的什么以及它如何解决问题。
      【解决方案5】:

      Ben Bank's answer 如果您希望按字典顺序排列组合,则效果很好。但是,如果您希望组合随机排序,这里有一个解决方案:

      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,那么结果会是这样的:

      index combination
      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 个组合。随机化顺序有助于缓解此问题(请参阅上面的示例输出)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-02
        • 2014-06-09
        • 2011-10-30
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多