Python:优化比较两个整数的列表理解

Python: Optimising a list comprehension which compares two integers

我有一个作用于两个整数列表的列表理解。它的作用类似于 itertools.product,带有过滤器以丢弃两者相等的元素并进行比较以对它们进行排序。

代码如下:

to_add = [(min(atom_1, atom_2), max(atom_1, atom_2))
          for atom_1 in atoms_1 for atom_2 in atoms_2
          if atom_2 != atom_1]
add_dict = coll.defaultdict(list)
for k, v in to_add:
    add_dict[k].append(v)

我在写的时候看到的最明显的事情是不需要先调用min再调用max。我真正想要的是 min 和另一个,但我想不出如何摆脱对 max.

的冗余调用

我对其进行了分析并得到了以下结果,这些结果代表了 10 次重复(read_amber.py 是总体函数调用的名称):

     62880808 function calls (62880792 primitive calls) in 14.746 seconds

     Ordered by: internal time

       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
           19    6.786    0.357   10.688    0.563 read_amber.py:256(add_exclusions)
     16431524    1.625    0.000    1.625    0.000 {min}
     16431511    1.295    0.000    1.295    0.000 {max}
       842947    1.051    0.000    1.051    0.000 {method 'format' of 'str' objects}
       842865    1.031    0.000    1.557    0.000 {filter}
     16457861    0.838    0.000    0.838    0.000 {method 'append' of 'list' objects}
            1    0.793    0.793    3.757    3.757 read_amber.py:79(write_to)
      8414872    0.526    0.000    0.526    0.000 read_amber.py:130(<lambda>)
      1685897    0.266    0.000    0.266    0.000 {method 'write' of 'file' objects}
        97489    0.142    0.000    0.142    0.000 {sorted}
            1    0.130    0.130    0.300    0.300 read_amber.py:32(read_from)
       247198    0.127    0.000    0.155    0.000 read_amber.py:134(data_cast)
848267/848263    0.042    0.000    0.042    0.000 {len}
            1    0.038    0.038    0.038    0.038 read_amber.py:304(update_exclusion_list)
       500352    0.028    0.000    0.028    0.000 {method 'lower' of 'str' objects}

有没有办法摆脱其中一个多余的 min/max 调用?还有其他明显的方法可以加快此代码段的速度吗?

我已经尝试过使用 itertools 生成器,但列表理解速度更快。我还尝试了 sorted 和必要的转换,但 min/max 比那快。

最后,我是 cProfile 的新手。按 'tottime' 排序是否合理?

怎么样:

import collections as coll
import itertools

add_dict = coll.defaultdict(list)
for atom_1, atom_2 in itertools.product(atoms_1, atoms_2):
    if atom_1 == atom_2: continue
    (atom_min, atom_max) = (atom_1, atom_2) if atom_1 < atom_2 else (atom_2, atom_1)
    add_dict[atom_min].append(atom_max)

或者,如果额外的作业是个问题(我认为这不重要):

add_dict = coll.defaultdict(list)
for atom_1, atom_2 in itertools.product(atoms_1, atoms_2):
    if atom_1 == atom_2: continue
    if atom_1 < atom_2:
        add_dict[atom_1].append(atom_2)
    else:
        add_dict[atom_2].append(atom_1)

虽然这看起来不太可读。


编辑: 时间结果:

看起来这种方法将运行时间减少了一半。

import collections as coll
import itertools

atoms_1 = [1,2,3,4,5,6]
atoms_2 = [2,4,6,1,2,3]

def old():
    to_add = [(min(atom_1, atom_2), max(atom_1, atom_2)) for atom_1 in atoms_1 for atom_2 in atoms_2 if atom_2 != atom_1]
    add_dict = coll.defaultdict(list)
    for k, v in to_add:
        add_dict[k].append(v)
    return add_dict

def new(): 
    add_dict = coll.defaultdict(list)
    for atom_1, atom_2 in itertools.product(atoms_1, atoms_2):
        if atom_1 == atom_2: continue
        (atom_min, atom_max) = (atom_1, atom_2) if atom_1 < atom_2 else (atom_2, atom_1)
        add_dict[atom_min].append(atom_max)    
    return add_dict

import timeit
print(timeit.timeit("old()", setup="from __main__ import old"))  # 20.76972103
print(timeit.timeit("new()", setup="from __main__ import new"))  # 10.9827100827

编辑 2: timeit 结果——更长的列表,更少的 timeit 迭代

atoms_1 = [1,2,3,4,5,6] * 5
atoms_2 = [2,4,6,1,2,3] * 5

print(timeit.timeit("old()", setup="from __main__ import old", number=100000)) # 46.2878425701
print(timeit.timeit("new()", setup="from __main__ import new", number=100000)) # 21.9272824532