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


当前回答

正如其他人所说,对于大型列表,in可能非常慢。这里比较了in, set和bisect的性能。注意时间(秒)是对数尺度。

测试代码:

import random
import bisect
import matplotlib.pyplot as plt
import math
import time


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


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


def method_bisect(a, b, c):
    start_time = time.time()
    b.sort()
    for i, x in enumerate(a):
        index = bisect.bisect_left(b, x)
        if index < len(a):
            if x == b[index]:
                c[i] = 1
    return time.time() - start_time


def profile():
    time_method_in = []
    time_method_set_in = []
    time_method_bisect = []

    # adjust range down if runtime is too long or up if there are too many zero entries in any of the time_method lists
    Nls = [x for x in range(10000, 30000, 1000)]
    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)]

        time_method_in.append(method_in(a, b, c))
        time_method_set_in.append(method_set_in(a, b, c))
        time_method_bisect.append(method_bisect(a, b, c))

    plt.plot(Nls, time_method_in, marker='o', color='r', linestyle='-', label='in')
    plt.plot(Nls, time_method_set_in, marker='o', color='b', linestyle='-', label='set')
    plt.plot(Nls, time_method_bisect, marker='o', color='g', linestyle='-', label='bisect')
    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc='upper left')
    plt.yscale('log')
    plt.show()


profile()

其他回答

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

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确实发生了变化,请提供更多关于您正在做什么的细节。

如果您只想检查列表中是否存在一个元素,

7 in list_data

是最快的解决方案。请注意

7 in set_data

是一个近乎自由的操作,与集合的大小无关!从一个大列表中创建一个set要比在列表中慢300到400倍,所以如果您需要检查许多元素,首先创建一个set会更快。

用perfplot创建的Plot:

import perfplot
import numpy as np


def setup(n):
    data = np.arange(n)
    np.random.shuffle(data)
    return data, set(data)


def list_in(data):
    return 7 in data[0]


def create_set_from_list(data):
    return set(data[0])


def set_in(data):
    return 7 in data[1]


b = perfplot.bench(
    setup=setup,
    kernels=[list_in, set_in, create_set_from_list],
    n_range=[2 ** k for k in range(24)],
    xlabel="len(data)",
    equality_check=None,
)
b.save("out.png")
b.show()

听起来您的应用程序可能会从使用Bloom Filter数据结构中获得优势。

简而言之,bloom过滤器查找可以非常快速地告诉你一个值是否绝对不存在于一个集合中。否则,您可以执行较慢的查找,以获得可能在列表中的值的索引。因此,如果您的应用程序倾向于获得“未找到”结果,而不是“找到”结果,您可能会通过添加Bloom Filter看到速度的提高。

关于细节,维基百科提供了Bloom过滤器如何工作的很好的概述,在网上搜索“python Bloom过滤器库”将提供至少两个有用的实现。