检查一个值是否存在于一个非常大的列表的最快方法是什么?


当前回答

def check_availability(element, collection: iter):
    return element in collection

使用

check_availability('a', [1,2,3,4,'a','b','c'])

我相信这是知道所选值是否在数组中的最快方法。

其他回答

你可以把你的物品放在一个集合里。设置查找非常有效。

Try:

s = set(a)
if 7 in s:
  # do stuff

在注释中,你说你想获取元素的索引。不幸的是,集合没有元素位置的概念。另一种方法是对列表进行预先排序,然后在每次需要查找元素时使用二分搜索。

请注意,in操作符不仅测试相等性(==),还测试恒等式(is),列表的in逻辑大致相当于以下内容(实际上是用C而不是Python编写的,至少在CPython中是这样):

对于s中的元素: 如果元素是目标: #快速检查身份意味着相等 还真 如果element == target: #慢速检查实际相等 还真 返回假

在大多数情况下,这个细节是无关紧要的,但在某些情况下,它可能会让Python新手感到惊讶,例如numpy。NAN具有不等于自身的不寻常性质:

>>> import numpy
>>> numpy.NAN == numpy.NAN
False
>>> numpy.NAN is numpy.NAN
True
>>> numpy.NAN in [numpy.NAN]
True

为了区分这些不寻常的情况,你可以使用任何(),比如:

>>> lst = [numpy.NAN, 1 , 2]
>>> any(element == numpy.NAN for element in lst)
False
>>> any(element is numpy.NAN for element in lst)
True 

注意,使用any()的列表的in逻辑将是:

any(element is target or element == target for element in lst)

然而,我应该强调这是一个边缘情况,对于绝大多数情况,in操作符是高度优化的,当然正是你想要的(无论是对列表还是对集合)。

a = [4,2,3,1,5,6]

index = dict((y,x) for x,y in enumerate(a))
try:
   a_index = index[7]
except KeyError:
   print "Not found"
else:
   print "found"

只有在a没有改变的情况下,这才会是一个好主意,因此我们可以只执行一次dict()部分,然后重复使用它。如果a确实发生了变化,请提供更多关于您正在做什么的细节。

最初的问题是:

知道一个值是否存在于一个列表(一个列表 它有数百万个值),它的索引是什么?

因此,有两件事需要发现:

是列表中的一项,和 索引是什么(如果在列表中)。

为此,我修改了@xslittlegrass代码来计算所有情况下的索引,并添加了一个额外的方法。

结果

方法是:

基本上,if x In b: return b。index(x) 在b.index(x)上尝试/捕获(跳过必须检查x是否在b中) Set——基本上如果x在Set (b):返回b.index(x) 平分——对b和它的下标排序,对x在排序(b)中进行二分搜索。 注意来自@ xsllittlegrass的mod,它返回排序后的b的下标, 而不是原来的b) 反向——为b形成一个反向查找字典d;然后 D [x]提供了x的索引。

结果表明,方法5速度最快。

有趣的是,try方法和set方法在时间上是等价的。


测试代码

import random
import bisect
import matplotlib.pyplot as plt
import math
import timeit
import itertools

def wrapper(func, *args, **kwargs):
    " Use to produced 0 argument function for call it"
    # Reference https://www.pythoncentral.io/time-a-python-function/
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

def method_in(a,b,c):
    for i,x in enumerate(a):
        if x in b:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_try(a,b,c):
    for i, x in enumerate(a):
        try:
            c[i] = b.index(x)
        except ValueError:
            c[i] = -1

def method_set_in(a,b,c):
    s = set(b)
    for i,x in enumerate(a):
        if x in s:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_bisect(a,b,c):
    " Finds indexes using bisection "

    # Create a sorted b with its index
    bsorted = sorted([(x, i) for i, x in enumerate(b)], key = lambda t: t[0])

    for i,x in enumerate(a):
        index = bisect.bisect_left(bsorted,(x, ))
        c[i] = -1
        if index < len(a):
            if x == bsorted[index][0]:
                c[i] = bsorted[index][1]  # index in the b array

    return c

def method_reverse_lookup(a, b, c):
    reverse_lookup = {x:i for i, x in enumerate(b)}
    for i, x in enumerate(a):
        c[i] = reverse_lookup.get(x, -1)
    return c

def profile():
    Nls = [x for x in range(1000,20000,1000)]
    number_iterations = 10
    methods = [method_in, method_try, method_set_in, method_bisect, method_reverse_lookup]
    time_methods = [[] for _ in range(len(methods))]

    for N in Nls:
        a = [x for x in range(0,N)]
        random.shuffle(a)
        b = [x for x in range(0,N)]
        random.shuffle(b)
        c = [0 for x in range(0,N)]

        for i, func in enumerate(methods):
            wrapped = wrapper(func, a, b, c)
            time_methods[i].append(math.log(timeit.timeit(wrapped, number=number_iterations)))

    markers = itertools.cycle(('o', '+', '.', '>', '2'))
    colors = itertools.cycle(('r', 'b', 'g', 'y', 'c'))
    labels = itertools.cycle(('in', 'try', 'set', 'bisect', 'reverse'))

    for i in range(len(time_methods)):
        plt.plot(Nls,time_methods[i],marker = next(markers),color=next(colors),linestyle='-',label=next(labels))

    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc = 'upper left')
    plt.show()

profile()
7 in a

最清晰最快的方法。

您也可以考虑使用一个集合,但是从列表中构造该集合所花费的时间可能比快速成员测试所节省的时间要多。唯一确定的方法就是做好基准测试。(这也取决于你需要什么操作)