python 中的成员资格测试比 set() 更快

Posted

技术标签:

【中文标题】python 中的成员资格测试比 set() 更快【英文标题】:faster membership testing in python than set() 【发布时间】:2011-10-29 22:20:15 【问题描述】:

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

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 )

【问题讨论】:

你实际得到了什么样的时间? 60 秒,c++ 中的算法(使用 tr1/unordered_set)在 18 秒内完成相同的操作... 一定要按顺序检查吗?创建两个集合,创建一个交集,然后从您正在检查成员资格的集合中减去该交集可能会更快。 这很模糊。您需要提供人们可以实际运行的可重现结果。 我刚刚看到你对速度的评论——Python 比 C++ 慢大约 3 倍。对于许多用途而言,这实际上对 Python 来说非常好。您是否分析过 Python 代码?有多少时间用于检查集合成员资格,有多少时间用于其他事情? 【参考方案1】:

set 是最快的。

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

如果您仍然遇到速度问题,您需要通过其他方式加速您的程序,例如使用 PyPy 而不是 cPython。

【讨论】:

PyPy 如何比 cPython 更快? speed.pypy.org。基本上,它实现了一个即时编译器,就像浏览器中的 javascript 引擎一样,可以极大地加速许多类型的代码。大多数情况下它的速度要快 2-100 倍。 你分析过你的代码吗?大部分时间是__contains__ 步骤吗?正如我在回答中所说的那样,“set 的速度很快。”除非您的问题出在其他地方,否则无法在 Python 中加速。 使用 fozenset() 而不是 set() 在我的代码(无关项目)中提高了 15% 的速度。 @ChaimG 根据这个***.com/questions/36555214/… 的答案,set 和 freezeset 的速度应该是完全相同的——好奇你是如何测试它的以及它有什么不同?【参考方案2】:

正如我在评论中指出的那样,可能让您放慢脚步的是您正在按顺序检查 sys.stdin 中的每一行是否属于您的“主”集。这将非常非常慢,并且不允许您利用集合操作的速度。举个例子:

#!/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)

以上在我使用了 5 年的机器上运行时间约为 6 秒,它正在测试比您需要的更大集合的成员资格(除非我误解了您)。大部分时间实际上都花在了创建集合上,所以你甚至不会有这样的开销。您所指的字符串很长的事实与此无关;正如 agf 所解释的,创建一个集合会创建一个哈希表。我怀疑(尽管再次,从您的问题中并不清楚)如果您可以将所有输入数据放入集合中在进行任何成员资格测试之前,它会快很多,而不是一次读取一项,然后检查集合成员身份

【讨论】:

【参考方案3】:

您应该尝试拆分数据以加快搜索速度。树结构可以让您快速找到数据是否存在。

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

这看起来像:

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 )

创建速度会慢一些,但搜索速度应该快得多(如果您的键的第一个字符分布良好且并不总是相同,我预计会快 20 倍)。

这是第一步,您可以对第二个字符执行相同的操作,依此类推,然后搜索将只是遍历每个字母的树...

【讨论】:

设置访问是 O(1),树如何让它更快? 嗯,你似乎是对的。我的错误我今天真的学到了很多东西,我认为一个集合只是一个没有两倍相同值的列表。你有什么网址可以让我找到更多相关信息吗?我在官方文档上找不到任何关于访问速度的信息。 看看secure.wikimedia.org/wikipedia/en/wiki/Hash_table,这就是集合和字典。 @JC Plessis:查看详细的 python 操作复杂性:wiki.python.org/moin/TimeComplexity【参考方案4】:

正如 urschrei 所述,您应该“矢量化”支票。 一次检查一百万个元素的存在(就像在 C 中所做的那样)比检查一个元素一百万次要快。

【讨论】:

以上是关于python 中的成员资格测试比 set() 更快的主要内容,如果未能解决你的问题,请参考以下文章

测试 Scala 类型类中的成员资格

Python Pandas 根据另一个集合(集合)的成员资格选择行

熊猫数据框列中的成员资格测试

结合多个成员资格测试[重复]

Python基础14 集合

基于Python中的多个条件进行过滤