値がリストに存在するかどうかを確認する最速の方法


816

値がリスト(その中に数百万の値を持つリスト)に存在するかどうか、およびそのインデックスが何であるかを知る最も速い方法は何ですか?

この例のように、リスト内のすべての値が一意であることを知っています。

私が試す最初の方法は(実際のコードでは3.8秒)です。

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

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

私が試す2番目の方法は(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秒かかり、2番目の方法は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])

5
Pythonでは、角括弧で囲まれたものは配列ではなくリストと呼ばれます。リストを使用するのではなく、セットを使用してください。または、リストを並べ替えてbisectモジュールを使用する
Steven Rumbalski

それで、あなたは本当にインデックスを調整する必要がありますか?または実際に注文は重要ではなく、メンバーシップテスト、交差点などを実行したいだけですか?つまり、実際に何をしようとしているのかによって異なります。セットはあなたのために働くかもしれません、そしてそれからそれらは本当に良い答えですが、あなたが示したコードからはわかりません。

2
おそらく、値ではなくそのインデックスが必要であることを質問で指定する必要があります。
ローマBodnarchuk 2011

私は自分の質問を編集して、何をしたいのかをより明確に説明しようとします...そう思います...
Jean-Francois Gallant

1
@StevenRumbalski:セットは複製コンテンツを含むことができず、ジーンはパーティクルの位置を保存したいので(x、y、zは同じである可能性があります)、この場合はセットを使用できません
Hieu Vo

回答:


1572
7 in a

それを行うための最も明確で最速の方法。

の使用を検討することもできsetますが、リストからそのセットを作成すると、メンバーシップのテストを高速化するよりも時間がかかる場合があります。確実にする唯一の方法は、よくベンチマークすることです。(これは、必要な操作にも依存します)


5
ただし、インデックスはありません。インデックスを取得すると、節約した分が犠牲になります。
rodrigo

6
のように:aの7の場合:b = a.index(7)?
Jean-Francois Gallant 2011

26
@StevenRumbalski:セットは、順序付けする必要がない場合(したがって、インデックスがある場合)のオプションにすぎません。そしてセット答えで明確に述べられています、それはまた OPがそれを尋ねたように質問への直接の答えを与えるだけです。これは-1の価値があるとは思いません。

私は自分の質問を編集して、何をしたいのかをより明確に説明しようとします...そう思います...
Jean-Francois Gallant

1
さて、私は実際のコードでメソッドを試してみましたが、おそらく値のインデックスを知る必要があるため、もう少し時間がかかります。2番目の方法では、それが存在するかどうかを確認し、同時にインデックスを取得します。
Jean-Francois Gallant

213

他の人が述べた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 = []

    Nls = [x for x in range(1000,20000,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(math.log(method_in(a,b,c)))
        time_method_set_in.append(math.log(method_set_in(a,b,c)))
        time_method_bisect.append(math.log(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.show()

15
このような回答のカットアンドペーストの実行可能コードが大好きです。他人の時間の数秒を保存するには、3輸入が必要になります:import random / import bisect / import matplotlib.pyplot as pltその後、呼び出し:profile()
kghastie

1
これはどのバージョンのpythonですか?
カウベルト

コードを取得するのは常に素晴らしいですが、
前に進むだけで

そして、控えめなrange()オブジェクトを忘れないでください。を使用するvar in [integer list]場合、range()オブジェクトが同じシーケンスをモデル化できるかどうかを確認してください。セットに非常に近いパフォーマンスですが、より簡潔です。
Martijn Pieters

37

アイテムをに入れることができますset。セット検索は非常に効率的です。

試してください:

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

編集コメントで、要素のインデックスを取得することを望んでいます。残念ながら、セットには要素の位置の概念がありません。別の方法としては、リストを事前に並べ替え、要素を見つける必要があるたびにバイナリ検索を使用します。


そしてその後、私がこの値のインデックスを知りたい場合、それは可能であり、あなたはそれを行う迅速な方法がありますか?
Jean-Francois Gallant、2011

@ Jean-FrancoisGallant:この場合、セットはあまり役に立ちません。リストを事前にソートしてから、バイナリ検索を使用できます。私の更新された答えを見てください。
NPE、2011

私は自分の質問を編集して、何をしたいのかをより明確に説明しようとします...そう思います...
Jean-Francois Gallant

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

使用法

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

これは、選択された値が配列内にあるかどうかを知る最も速い方法だと思います。


71
return 'a' in a
霧雨

4
コードを定義に入れる必要があります:def listValue():a = [1,2,3,4、 'a'、 'b'、 'c'] return 'a' in ax = listValue()print( x)
Tenzin

12
それは有効なPythonの答えであり、単に良くない、読みやすいコードではありません。
リックヘンダーソン

1
注意してください!これはおそらくあなたがo='--skip'; o in ("--skip-ias"); # returns True !
Alex F

3
@Alex F in演算子は、同じ方法で部分文字列メンバーシップをテストします。ここで紛らわしい部分はおそらく("hello")単一値のタプルではありません("hello",)が、コンマが違いを生みます。o in ("--skip-ias",)あるFalse予想通り。
MoxieBall

16
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()の部分を1回実行してから繰り返し使用できます。が変更された場合は、何を行っているか詳細をお知らせください。


それは機能していますが、私のコードに実装されている場合は機能しません: "TypeError:unhashable type: 'list'
Jean-Francois Gallant

1
@ Jean-FrancoisGallant、それはおそらく、タプルを実際に使用する必要がある場所でリストを使用しているためです。コードを高速化する方法に関する包括的なアドバイスが必要な場合は、codereview.stackexchange.comに投稿してください。そこでは、スタイルとパフォーマンスのアドバイスが得られます。
Winston Ewert 2011

これは問題に対する非常に賢い解決策です。try except構成の代わりに、私は次のようにします:a_index = index.get(7)これは、キーが見つからない場合、デフォルトでNoneになります。
murphsp1 2014

14

元の質問は:

値がリスト(その中に数百万の値を持つリスト)に存在するかどうか、およびそのインデックスが何であるかを知る最も速い方法は何ですか?

したがって、2つのことを見つける必要があります。

  1. リスト内のアイテムであり、
  2. インデックスは何ですか(リストにある場合)。

これに向けて、@ xslittlegrassコードを変更してすべてのケースでインデックスを計算し、メソッドを追加しました。

結果

ここに画像の説明を入力してください

メソッドは次のとおりです。

  1. in-基本的にx in bの場合:b.index(x)を返します
  2. try--b.index(x)で試行/キャッチ(xがbであるかどうかを確認する必要がありません)
  3. set-基本的にset(b)のxの場合:b.index(x)を返します
  4. bisect-インデックスbでソートb、sorted(b)でxを二分探索します。元のbではなく、ソートされたbのインデックスを返す@xslittlegrassのmodに注意してください。
  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()

説明のタイプミス(「逆ループアップ」は「逆ルックアップ」である必要がありますか?)
Cam U

@ CamU--はい、修正しました。お知らせいただきありがとうございます。
DarrylG

7

アプリケーションがブルームフィルターデータ構造を使用することでメリットを得られるようです。

要するに、値がセットに存在しない場合、ブルームフィルタールックアップは非常にすばやく通知します。それ以外の場合は、検索を遅くして、リストにある可能性のある値のインデックスを取得できます。したがって、アプリケーションが「見つかりません」という結果よりも「見つかりません」という結果を取得する傾向が高い場合は、ブルームフィルターを追加することで速度が向上する可能性があります。

詳細については、ウィキペディアでブルームフィルターの仕組みの概要がわかり、「pythonブルームフィルターライブラリ」をWeb検索すると、少なくとも2つの便利な実装が提供されます。


7

in演算子は等価(==)だけでなく同一性(is)もテストすることに注意してください。s のinロジックは次のコードlistほぼ同等です(ただし、実際にはCではなく、少なくともCPythonではPythonで記述されています)。

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があります。たとえば、Python はそれ自体とは異なるという異常な特性を持っています

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

s のinロジックは次listのようにany()なることに注意してください。

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

ただし、これはエッジケースであることを強調する必要があります。ほとんどの場合、in演算子は高度に最適化されており、まさに(a listまたはaを使用してset)必要なものです。


NAN == NANがfalseを返すことは、異常ではありません。これは、IEEE 754標準で定義されている動作です。
TommyD

2

または使用__contains__

sequence.__contains__(value)

デモ:

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

2

@Winston Ewertのソリューションでは、非常に大きなリストの速度が大幅に向上しますが、このstackoverflowの回答は、exceptブランチに頻繁に到達すると、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)メソッドは、キーが辞書にあることを保証できない場合にのみ使用できます。key 存在する場合は値を返します(そうするdict[key]場合と同様)。存在しない場合.get()はデフォルト値を返します(ここではNone)。この場合、選択したデフォルトがにないことを確認する必要がありますa


1

これはコードではなく、非常に高速な検索のためのアルゴリズムです。

リストと探している値がすべて数字の場合、これはかなり簡単です。文字列の場合:下を見てください:

  • -リストの長さを「n」にしましょう
  • -オプションの手順:要素のインデックスが必要な場合:要素の現在のインデックス(0からn-1)を含むリストに2番目の列を追加します-後で参照
  • リストまたはそのコピーを注文する(.sort())
  • ループスルー:
    • 番号をリストのn / 2番目の要素と比較します
      • 大きい場合は、インデックスn / 2-n間で再度ループします
      • 小さい場合は、インデックス0-n / 2の間で再度ループします
      • 同じ場合:あなたはそれを見つけました
  • リストが見つかるか、番号が2つだけになるまでリストを絞り込みます(探している番号の下と上)。
  • これは1.000.000のリストに対して最大19ステップで要素を見つけます(正確にはlog(2)n)。

番号の元の位置も必要な場合は、2番目のインデックス列で探します。

リストが数字で構成されていない場合でも、メソッドは機能し、最速になりますが、文字列を比較/順序付けできる関数を定義する必要がある場合があります。

もちろん、これにはsorted()メソッドの投資が必要ですが、同じリストをチェックに再利用し続ける場合は、価値があるかもしれません。


26
説明したアルゴリズムは単純なバイナリ検索であることを忘れていました。
diugalde 2016

0

質問が常に最速の技術的方法として理解されるとは限らないため-私は常に理解/作成するための最も簡単で最速の方法を提案します:リスト内包、ワンライナー

[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

これにより、インデックスが適切なリストで返されます。

この問題をチェックする方法は他にもありますが、リストの内包は十分に速く、問題を解決するのに十分な速さでそれを書くという事実に加えます。


-2

私にとっては、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

-2

積がkに等しい配列に2つの要素が存在するかどうかを確認するコード:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.