intersection()和'object for object in set if object in other_set'之间的速度差异

7

这两种方法哪一种更快?是否有一种“更好”的方法?基本上我有两个集合,最终想从这两个列表中得到一个匹配项。所以实际上,我的for循环更像是:

for object in set:
    if object in other_set:
        return object

就像我说的一样 - 我只需要一个匹配项,但我不确定如何处理intersection(),所以我不知道它是否更好。此外,如果有帮助的话,other_set是一个近10万个组件的列表,而set可能只有几百个,最多几千个。


1
这可能取决于有多少匹配项。intersection会一直寻找,直到找到所有匹配项,但另一方面,intersection是用C实现的,所以实际代码运行速度更快。 - Thomas K
对于那些喜欢一行代码的人:filter(other_set.__contains__, some_set) 和差异 filter(lambda x: other_set.__contains__(x), some_set) - 0 _
它们之间是否有任何已知的关联?一个是否是另一个子集(正如被接受的解决方案中所测试的那样)? - Jeremy West
4个回答

8
from timeit import timeit

setup = """
from random import sample, shuffle
a = range(100000)
b = sample(a, 1000)
a.reverse()
"""

forin = setup + """
def forin():
    # a = set(a)
    for obj in b:
        if obj in a:
            return obj
"""

setin = setup + """
def setin():
    # original method:
    # return tuple(set(a) & set(b))[0]
    # suggested in comment, doesn't change conclusion:
    return next(iter(set(a) & set(b)))
"""

print timeit("forin()", forin, number = 100)
print timeit("setin()", setin, number = 100)

时间:

>>>
0.0929054012768
0.637904308732
>>>
0.160845057616
1.08630760484
>>>
0.322059185123
1.10931801261
>>>
0.0758695262169
1.08920981403
>>>
0.247866360526
1.07724461708
>>>
0.301856152688
1.07903130641

在设置中将它们制作成集合,并运行10000次而不是100次,可以产生更好的效果。
>>>
0.000413064976328
0.152831597075
>>>
0.00402408388788
1.49093627898
>>>
0.00394538156695
1.51841512101
>>>
0.00397715579584
1.52581949403
>>>
0.00421472926155
1.53156769646

所以无论是否将它们转换为集合,您的版本都要快得多。

感谢您提供的精彩分析 - 实际上不需要进行设置转换,因为我大部分使用集合的操作都是在第一次创建集合时完成的,但我明白您的意思。再次意识到我应该好好看看timeit - 我本应该想到这一点的。再次感谢! - Jon Phenow
1
尝试使用next(x for x in the_list if x in the_set) - Jochen Ritzel
你的第二个结果和我的不一样,我得到了很多变化(特别是在nextfor/if in测试之间)。但在我所有的测试中,在函数调用之前将其转换为一个集合是更好的选择,可以大大提高速度。 - milkypostman
我怀疑如果你使用next(iter(set(a) & set(b))),你可以跳过在 tuple(set(a) & set(b))[0] 中创建大元组的开销。 - Brandon Rhodes
@BrandonRhodes 我刚试了一下。对于第一个测试来说,没有实质性的区别(因为时间被“set”创建时间所占据)。对于第二个测试来说,它比使用“tuple”快3-4倍,但仍然比“for”/“if”慢几个数量级。 - agf
1
我想要提醒未来的人,这会返回两个集合中共有的一个对象。找到第一个对象很可能比运行完整个intersection()函数更好,后者会返回在两个集合中都存在的所有对象。 - std''OrgnlDave

5

我知道这是一篇较旧的帖子。但我来到这里是想查找使用intersectionin进行性能比较,并认为值得添加更多信息。上面的答案很好,但对于实际最佳方案让我感到不清楚。

"第一个结果"解决方案不能解决我的特定用例。

相反,我想知道如何使用离散方法生成产生相同结果集的不同实现的性能表现。而不仅仅是第一个交集值。因此,我在下面包含了代码来执行选项评估并进行1000次循环测试。与@agf发表的相反,当期望输出是匹配列表时,使用set速度要快得多。

我的测试结果是:

runForin took 132851.600ms
runForinBlist took 37700.916ms
True
runForInListComp took 132783.147ms
True
runForinSet took 780.919ms
True
runSetIntersection took 760.980ms (WINNER)
True
runSetin took 771.921ms
True

这里是代码,希望能对某些人有所帮助。注意:我还评估了blist (http://stutzbachenterprises.com/blist/blist.html)库,因为它在其他用例中表现相当出色。

import time
from random import sample, shuffle
from blist import blist

a = range(100000)
aBlist = blist([i for i in a])

b = sample(a, 1000)
a.reverse()

def print_timing(func):
    def wrapper(*arg):
        t1 = time.time()
        res = func(*arg)
        t2 = time.time()
        print '%s took %0.3fms' % (func.func_name, (t2-t1)*1000.0)
        return res
    return wrapper


def forIn():
    ret = []
    for obj in b:
        if obj in a:
            ret.append(obj)
    return ret

def forInBlist():
    ret = []
    for obj in b:
        if obj in aBlist:
            ret.append(obj)
    return ret


def forInListComp():
    return [value for value in b if value in a] 


def forInSet():
    ret = []
    for obj in b:
        if obj in set(a):
            ret.append(obj)
    return ret


def setIntersection(): 
    return set(a).intersection(b) 


def setIn():
    return list(set(a) & set(b))


@print_timing
def runForIn(times):
    for i in range(times):
        ret = forIn()
    return ret
        
@print_timing
def runForInBlist(times):
    for i in range(times):
        ret = forInBlist()
    return ret

@print_timing
def runForInListComp(times):
    for i in range(times):
        ret = forInListComp()
    return ret

@print_timing
def runForInSet(times):
    for i in range(times):
        ret = forInSet()
    return ret

@print_timing
def runSetIntersection(times):
    for i in range(times):
        ret = setIntersection()
    return ret

@print_timing
def runSetIn(times):
    for i in range(times):
        ret = setIn()
    return ret

def checkResults(results):
    master = None
    for resultSet in results:
        if not master:
            master = sorted(list(resultSet))
            continue
        try:
            if master != sorted(list(resultSet)):
                return False, master, sorted(list(resultSet))
        except:
            print resultSet
            return False
    return True

iterations = 5
results = []
runForInResults = runForIn(iterations)
results.append(runForInResults)

runForInBlistResults = runForInBlist(iterations)
results.append(runForInBlistResults)
print checkResults(results)

runForInListCompResults = runForInListComp(iterations)
results.append(runForInListCompResults)
print checkResults(results)

runForInSetResults = runForInSet(iterations)
results.append(runForInSetResults)
print checkResults(results)

runSetIntersectionResults = runSetIntersection(iterations)
results.append(runSetIntersectionResults)
print checkResults(results)

runSetInResults = runSetIn(iterations)
results.append(runSetInResults)
print checkResults(results)

2

你的代码很好。对于集合,使用if object in other_set进行项目查找非常高效。


0

我写了一个简单的工具,用来检查两个集合是否至少有一个共同元素。今天我遇到了同样的优化问题,你的帖子救了我的一天。这只是一种感谢你指出这一点的方式,希望这也能帮助其他人:)

注意:该工具不会返回第一个共同元素,而是在它们至少有一个共同元素时返回true,否则返回false。当然,它可以很容易地被修改以满足你的目标。

def nonEmptyIntersection(A, B):
    """
    Returns true if set A intersects set B.
    """
    smaller, bigger = A, B
    if len(B) < len(A):
        smaller, bigger = bigger, smaller
    for e in smaller:
        if e in bigger:
            return True
    return False

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