簡體   English   中英

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

[英]Fastest way to check if a value exists in a list

檢查一個值是否存在於一個非常大的列表中的最快方法是什么?

7 in a

最清晰和最快的方法。

您也可以考慮使用set ,但是從您的列表中構建該 set 可能需要比更快的成員資格測試節省的時間更多。 唯一可以確定的方法是做好基准測試。 (這也取決於您需要什么操作)

正如其他人所說,對於大型列表, in可能非常慢。 以下是insetbisect的一些性能比較。 請注意時間(以秒為單位)是對數刻度。

在此處輸入圖像描述

測試代碼:

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

您可以將您的項目放入一個set中。 集合查找非常有效。

嘗試:

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

編輯在評論中您說您想獲取元素的索引。 不幸的是,集合沒有元素位置的概念。 另一種方法是對列表進行預排序,然后在每次需要查找元素時使用二進制搜索

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

用法

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

我相信這是了解所選值是否在數組中的最快方法。

原來的問題是:

知道一個值是否存在於一個列表(一個包含數百萬個值的列表)中以及它的索引是什么的最快方法是什么?

因此,有兩件事要找到:

  1. 是列表中的一個項目,並且
  2. 什么是索引(如果在列表中)。

為此,我修改了@xslittlegrass 代碼以在所有情況下計算索引,並添加了一個附加方法。

結果

在此處輸入圖像描述

方法是:

  1. in-- 基本上如果 x in b: return b.index(x)
  2. try--try/catch on b.index(x) (跳過檢查 x 是否在 b 中)
  3. set--基本上如果 x 在 set(b) 中:返回 b.index(x)
  4. bisect-- 用它的索引對 b 排序,在 sorted(b) 中對 x 進行二分搜索。 注意來自 @xslittlegrass 的 mod,它返回排序后的 b 中的索引,而不是原始 b)
  5. reverse--為b形成一個反向查找字典d; 然后 d[x] 提供 x 的索引。

結果表明方法5是最快的。

有趣的是, tryset方法在時間上是等價的。


測試代碼

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()
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() 部分,然后重復使用它。 如果確實發生了變化,請提供有關您正在做什么的更多詳細信息。

請注意, in運算符不僅測試相等 ( == ) 還測試身份 ( is ), listin邏輯大致等價於以下內容(盡管它實際上是用 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具有不等於自身的不尋常屬性:

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

請注意,帶有any()listin邏輯將是:

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

但是,我應該強調這是一個邊緣情況,並且對於絕大多數情況, in運算符都經過高度優化,並且當然正是您想要的(使用list或使用set )。

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

7 in list_data

是最快的解決方案。 請注意,盡管

7 in set_data

是一種近乎自由的操作,與集合的大小無關! 從大列表創建集合比in慢 300 到 400 倍,因此如果需要檢查許多元素,首先創建集合更快。

在此處輸入圖像描述

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

聽起來您的應用程序可能會從使用 Bloom Filter 數據結構中獲益。

簡而言之,布隆過濾器查找可以非常快速地告訴您某個值是否絕對不存在於集合中。 否則,您可以進行較慢的查找以獲取可能在列表中的值的索引。 因此,如果您的應用程序往往比“找到”結果更頻繁地獲得“未找到”結果,您可能會通過添加布隆過濾器看到加速。

有關詳細信息,Wikipedia 很好地概述了布隆過濾器的工作原理,並且在網絡上搜索“python 布隆過濾器庫”將提供至少幾個有用的實現。

或使用__contains__

sequence.__contains__(value)

演示:

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

這不是代碼,而是用於非常快速搜索的算法。

如果您的列表和您要查找的值都是數字,那么這非常簡單。 如果字符串:查看底部:

  • -讓“n”成為列表的長度
  • -可選步驟:如果您需要元素的索引:將第二列添加到具有當前元素索引(0到n-1)的列表中 - 見下文
  • 訂購您的列表或它的副本 (.sort())
  • 依次通過:
    • 將您的號碼與列表的第 n/2 個元素進行比較
      • 如果更大,則在索引 n/2-n 之間再次循環
      • 如果更小,則在索引 0-n/2 之間再次循環
      • 如果相同:你找到了
  • 繼續縮小列表,直到找到它或只有 2 個數字(在您要查找的數字的下方和上方)
  • 這將在最多 19 個步驟中找到 1.000.000 列表中的任何元素(准確地說是 log(2)n)

如果您還需要號碼的原始位置,請在第二個索引列中查找。

如果您的列表不是由數字組成的,該方法仍然有效並且速度最快,但您可能需要定義一個可以比較/排序字符串的函數。

當然,這需要 sorted() 方法的投入,但如果你一直重復使用同一個列表進行檢查,這可能是值得的。

因為問題並不總是應該被理解為最快的技術方式 - 我總是建議最直接的最快方式來理解/編寫:列表理解,單行

[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中項目的索引。

這將返回一個漂亮列表中的索引。

還有其他方法可以檢查這個問題 - 但是列表推導足夠快,加上編寫它足夠快的事實,以解決問題。

@Winston Ewert的解決方案極大地提高了非常大的列表的速度,但是這個stackoverflow答案表明,如果經常到達除外分支,則try:/ except:/ else:構造將變慢。 另一種方法是將.get()方法用於dict:

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

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

我覺得很好

mylist = [j for j in range(100)]
value = 13 #mutable
print (value in mylist)
#output: True

如果你想打印值:

mylist = [j for j in range(100)]
value = 13 #mutable
if value in mylist:
    print (value)

空間數據的邊緣情況

可能有更快的算法來處理空間數據(例如重構以使用 kd 樹),但檢查向量是否在數組中的特殊情況很有用:

  • 如果你有空間數據(即笛卡爾坐標)
  • 如果你有integer個掩碼(即數組過濾)

在這種情況下,我想知道由兩個點定義的(無向)邊是否在(無向)邊的集合中,這樣

(pair in unique_pairs) | (pair[::-1] in unique_pairs) for pair in pairs

其中pair構成任意長度的兩個向量(即形狀(2,N) )。

如果這些向量之間的距離有意義,那么測試可以用浮點不等式表示

test_result = Norm(v1 - v2) < Tol

並且“列表中存在值”只是any(test_result)

integer 對和 R3 向量對的示例代碼和虛擬測試集生成器如下所示。

# 3rd party
import numpy as np
import numpy.linalg as LA
import matplotlib.pyplot as plt

# optional
try:
    from tqdm import tqdm
except ModuleNotFoundError:
    def tqdm(X, *args, **kwargs):
        return X
    print('tqdm not found. tqdm is a handy progress bar module.')
    

def get_float_r3_pairs(size):
    """ generate dummy vector pairs in R3  (i.e. case of spatial data) """
    coordinates = np.random.random(size=(size, 3))
    pairs = []
    for b in coordinates:
        for a in coordinates:
            pairs.append((a,b))
    pairs = np.asarray(pairs)
    return pairs
    
        
def get_int_pairs(size):
    """ generate dummy integer pairs (i.e. case of array masking) """
    coordinates = np.random.randint(0, size, size)
    pairs = []
    for b in coordinates:
        for a in coordinates:
            pairs.append((a,b))
    pairs = np.asarray(pairs)
    return pairs


def float_tol_pair_in_pairs(pair:np.ndarray, pairs:np.ndarray) -> np.ndarray:
    """
    True if abs(a0 - b0) <= tol & abs(a1 - b1) <= tol for (ai1, aj2), (bi1, bj2)
    in [(a01, a02), ... (aik, ajl)]
    
    NB this is expected to be called in iteration so no sanitization is performed.

    Parameters
    ----------
    pair : np.ndarray
        pair of vectors with shape (2, M)
    pairs : np.ndarray
        collection of vector pairs with shape (N, 2, M)

    Returns
    -------
    np.ndarray
        (pair in pairs) | (pair[::-1] in pairs).
    """
    m1 = np.sum( abs(LA.norm(pairs - pair, axis=2)) <= (1e-03, 1e-03), axis=1 ) == 2
    m2 = np.sum( abs(LA.norm(pairs - pair[::-1], axis=2)) <= (1e-03, 1e-03), axis=1 ) == 2
    return m1 | m2


def get_unique_pairs(pairs:np.ndarray) -> np.ndarray:
    """
    apply float_tol_pair_in_pairs for pair in pairs
    
    Parameters
    ----------
    pairs : np.ndarray
        collection of vector pairs with shape (N, 2, M)

    Returns
    -------
    np.ndarray
        pair if not ((pair in rv) | (pair[::-1] in rv)) for pair in pairs

    """
    pairs = np.asarray(pairs).reshape((len(pairs), 2, -1))
    rv = [pairs[0]]
    for pair in tqdm(pairs[1:], desc='finding unique pairs...'):
        if not any(float_tol_pair_in_pairs(pair, rv)):
            rv.append(pair)
    return np.array(rv)

計時結果

對我來說,這是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

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

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

我嘗試的第一種方法是(在我的實際代碼中為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"

堆棧溢出用戶建議的方法(我的實際代碼為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])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM