关于性能:python中的成员资格测试比set()更快

faster membership testing in python than set()

我必须检查包含10-100K元素的列表中是否存在数百万个元素(20-30个字母str)。在python中,有没有比set()更快的方法做到这一点?

1
2
3
4
5
6
7
8
9
10
11
import sys
#load ids
ids = set( x.strip() for x in open(idfile) )

for line in sys.stdin:
    id=line.strip()
    if id in ids:
        #print fastq
        print id
        #update ids
        ids.remove( id )


set是最快的。

但是,如果您重写代码以创建一次set,而不更改它,则可以使用frozenset内置类型。它完全一样,除了不变。

如果您仍然有速度问题,您需要以其他方式加速您的程序,例如使用pypy而不是cpython。


正如我在我的评论中所指出的,可能会让你放慢脚步的是,你按顺序检查来自sys.stdin的每一行,以获得你的"主人"集合的成员资格。这将非常非常非常缓慢,并且不允许您利用设置操作的速度。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python

import random

# create two million-element sets of random numbers
a = set(random.sample(xrange(10000000),1000000))
b = set(random.sample(xrange(10000000),1000000))
# a intersection b
c = a & b
# a difference c
d = list(a - c)
print"set d is all remaining elements in a not common to a intersection b"
print"length of d is %s" % len(d)

在我那台五岁的机器上,这台机器运行了大约6个小时,它正在测试比你要求的更多的会员资格(除非我误解了你)。大部分时间实际上都花在了创建集合上,所以您甚至不会有这样的开销。您所引用的字符串很长这一事实在这里并不相关;正如AGF解释的那样,创建一个集合会创建一个哈希表。我怀疑(不过,你的问题也不清楚),如果你能在做任何成员资格测试之前把所有输入数据都输入到一个集合中,它会更快,而不是一次读取一个项目,然后检查集合成员资格。


您应该尝试拆分数据以加快搜索速度。如果数据存在或不存在,树结构将允许您快速查找。

例如,从一个简单的映射开始,它将第一个字母与以该字母开头的所有键链接起来,因此您不必搜索所有键,只需搜索其中的一小部分。

如下所示:

1
2
3
4
5
6
7
8
9
10
11
ids = {}
for id in open(idfile):
    ids.setdefault(id[0], set()).add(id)

for line in sys.stdin:
    id=line.strip()
    if id in ids.get(id[0], set()):
       #print fastq
       print id
       #update ids
       ids[id[0]].remove( id )

创建速度会慢一点,但搜索速度会快得多(如果您的键的fisrt字符分布得很好并且不总是相同的话,我希望速度会快20倍)。

这是第一步,你可以用第二个字符做同样的事情,等等,然后搜索将只需要用每个字母在树上行走…


正如Urschrei所提到的,您应该"向量化"支票。一次检查一百万个元素的存在(如在C中所做的)比一百万次检查一个元素要快。