检查列表中是否存在值的最快方法

时间:2011-09-27 15:23:27

标签: python performance list

了解列表中是否存在值(包含数百万个值的列表)及其索引是什么的最快方法是什么?

我知道列表中的所有值都是唯一的,如本例所示。

我尝试的第一种方法是(在我的实际代码中为3.8秒):

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

if a.count(7) == 1:
    b=a.index(7)
    "Do something with variable b"

我尝试的第二种方法是(快2倍:我的真实代码为1.9秒):

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

try:
    b=a.index(7)
except ValueError:
    "Do nothing"
else:
    "Do something with variable b"

Stack Overflow用户提出的方法(我的实际代码为2.74秒):

a = [4,2,3,1,5,6]
if 7 in a:
    a.index(7)

在我的真实代码中,第一种方法需要3.81秒,第二种方法需要1.88秒。 这是一个很好的改进,但是:

我是Python /脚本的初学者,是否有更快的方法来做同样的事情并节省更多的处理时间?

我的应用程序的更具体说明:

在Blender API中,我可以访问粒子列表:

particles = [1, 2, 3, 4, etc.]

从那里,我可以访问粒子的位置:

particles[x].location = [x,y,z]

对于每个粒子,我通过搜索每个粒子位置来测试邻居是否存在:

if [x+1,y,z] in particles.location
    "Find the identity of this neighbour particle in x:the particle's index
    in the array"
    particles.index([x+1,y,z])

15 个答案:

答案 0 :(得分:1291)

7 in a

最清晰,最快捷的方式。

您也可以考虑使用set,但从列表中构建该集可能需要比更快的成员资格测试节省更多的时间。唯一可以确定的方法是做好基准测试。 (这还取决于您需要的操作)

答案 1 :(得分:32)

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

<强>用法

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

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

答案 2 :(得分:27)

您可以将商品放入set。设置查找非常有效。

尝试:

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

编辑在评论中,您说您想要获取元素的索引。不幸的是,集合没有元素位置的概念。另一种方法是对列表进行预排序,然后在每次需要查找元素时使用binary search

答案 3 :(得分:15)

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"

这只是一个好主意,如果一个不改变,因此我们可以做一次dict()部分然后重复使用它。如果确实有变化,请提供有关您正在做的事情的更多详细信息。

答案 4 :(得分:6)

听起来您的应用程序可能会因使用Bloom Filter数据结构而获益。

简而言之,布隆过滤器查找可以非常快速地告诉您一个值是否一定不存在于一个集合中。否则,您可以执行较慢的查找以获取列表中可能值很高的值的索引。因此,如果您的应用程序倾向于更频繁地获得“未找到”结果,那么“找到”结果,您可能会通过添加布隆过滤器来加快速度。

有关详细信息,维基百科可以很好地概述Bloom Filters的工作方式,而对“python bloom filter library”的网络搜索将提供至少一些有用的实现。

答案 5 :(得分:4)

请注意in运算符不仅测试相等(==)而且测试身份(is),in s的list逻辑是roughly equivalent to以下(它实际上是用C编写的,而不是Python,至少在CPython中):

for element in s:
    if element is target:
        # fast check for identity implies equality
        return True
    if element == target:
        # slower check for actual equality
        return True
return False

在大多数情况下,这个细节是无关紧要的,但在某些情况下,它可能让Python新手感到惊讶,例如,numpy.NAN具有not being equal to itself的不寻常属性:

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

要区分这些不寻常的情况,您可以使用any(),如:

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

请注意inlist的{​​{1}}逻辑是:

any()

但是,我应该强调这是一个边缘情况,对于绝大多数情况,any(element is target or element == target for element in lst) 运算符是高度优化的,当然正是您想要的(使用in或者一个list)。

答案 6 :(得分:4)

最初的问题是:

  

知道列表中是否存在值的最快方法是什么(列表   包含数百万个值)及其索引是什么?

因此有两件事可以找到:

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

为此,我修改了@xslittlegrass代码以在所有情况下计算索引,并添加了其他方法。

结果

enter image description here

方法是:

  1. in-基本是b中的x:返回b.index(x)
  2. try--尝试/捕获b.index(x)(无需检查b中的x)
  3. set-基本上是如果set(b)中的x:返回b.index(x)
  4. bisect-用索引对其b进行排序,在sorted(b)中对x进行二进制搜索。 请注意@xslittlegrass的mod,它返回排序后的b中的索引, 而不是原始的b)
  5. reverse-为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 :(得分:2)

这不是代码,而是快速搜索的算法。

如果您的列表和您要查找的值都是数字,那么这非常简单。如果是字符串:请查看底部:

  • -Let&#34; n&#34;是列表的长度
  • - 可选步骤:如果您需要元素的索引:使用元素的当前索引(0到n-1)将第二列添加到列表中 - 请参阅稍后
  • 订购您的清单或其副本(.sort())
  • 循环:
    • 将您的号码与列表的第n / 2个元素进行比较
      • 如果更大,则在索引n / 2-n
      • 之间再次循环
      • 如果更小,则在索引0-n / 2
      • 之间再次循环
      • 如果相同:你找到了
  • 继续缩小列表范围,直到找到它或只有2个数字(低于和高于您要查找的数字)
  • 这将找到最多19个步骤中的任何元素,列表为1.000.000 (准确地说是log(2)n)

如果您还需要数字的原始位置,请在第二个索引列中查找。

如果您的列表不是数字,则该方法仍然有效并且速度最快,但您可能需要定义一个可以比较/排序字符串的函数。

当然,这需要投入sorted()方法,但是如果你继续重复使用相同的列表进行检查,那么它可能是值得的。

答案 8 :(得分:2)

或使用__contains__

sequence.__contains__(value)

演示:

>>> l=[1,2,3]
>>> l.__contains__(3)
True
>>> 

答案 9 :(得分:1)

对我来说它是0.030秒(真实),0.026秒(用户)和0.004秒(系统)。

try:
print("Started")
x = ["a", "b", "c", "d", "e", "f"]

i = 0

while i < len(x):
    i += 1
    if x[i] == "e":
        print("Found")
except IndexError:
    pass

答案 10 :(得分:0)

present = False
searchItem = 'd'
myList = ['a', 'b', 'c', 'd', 'e']
if searchItem in myList:
   present = True
   print('present = ', present)
else:
   print('present = ', present)

答案 11 :(得分:0)

检查乘积等于k的数组中是否存在两个元素的代码:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)

答案 12 :(得分:0)

@Winston Ewert的解决方案极大地提高了超大型列表的速度,但是this stackoverflow answer表示,如果经常到达else分支,则try:/ except:/ else:构造将变慢。另一种方法是利用dict的.get()方法:

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

index = dict((y, x) for x, y in enumerate(a))

b = index.get(7, None)
if b is not None:
    "Do something with variable b"

.get(key, default)方法仅适用于无法保证键将包含在字典中的情况。如果存在键 ,它将返回值(与dict[key]一样),但是如果不存在,则.get()将返回您的默认值(此处为None)。在这种情况下,您需要确保所选的默认值不在a中。

答案 13 :(得分:0)

这对我有用:(列表理解,单线)

[list_to_search_in.index(i) for i in list_from_which_to_search if i in list_to_search_in]

我有一个包含所有项目的list_to_search_in,并想返回list_from_which_to_search中这些项目的索引。

这会在一个漂亮的列表中返回索引。

答案 14 :(得分:0)

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

7 in list_data

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

7 in set_data

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

enter image description here

使用 perfplot 创建的图:

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()