短い答え:を使用しますnot set(a).isdisjoint(b)
。これが一般的に最速です。
2つのリストをテストし、アイテムa
をb
共有する一般的な方法は4つあります。最初のオプションは、両方をセットに変換し、それらの交差をチェックすることです。
bool(set(a) & set(b))
セットはPythonのハッシュテーブルを使用して保存されるO(1)
ため、検索はそうです(Pythonの演算子の複雑さについては、こちらを参照してください)。理論的には、これがあるO(n+m)
ために、平均してn
およびm
リスト内のオブジェクトa
とb
。ただし、1)最初にリストからセットを作成する必要があります。これには無視できない時間がかかる可能性があります。2)ハッシュの衝突がデータ間でまばらであると想定されます。
これを行う2番目の方法は、次のようなリストで反復を実行するジェネレータ式を使用することです。
any(i in a for i in b)
これにより、インプレースで検索できるため、中間変数に新しいメモリが割り当てられません。また、最初の発見時に救済します。ただし、in
演算子は常にO(n)
リストにあります(ここを参照)。
別の提案されたオプションは、リストの1つを反復して、セット内の他の1つを変換し、次のようにこのセットのメンバーシップをテストするhybridtoです。
a = set(a); any(i in a for i in b)
4番目のアプローチはisdisjoint()
、(凍結された)セット(ここを参照)のメソッドを利用することです。次に例を示します。
not set(a).isdisjoint(b)
検索する要素が配列の先頭近くにある場合(並べ替えられている場合など)、セットの交差メソッドは中間変数に新しいメモリを割り当てる必要があるため、ジェネレータ式が優先されます。
from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974
リストサイズの関数でのこの例の実行時間のグラフを次に示します。
両方の軸が対数であることに注意してください。これは、ジェネレータ式の最良のケースを表しています。ご覧のとおり、isdisjoint()
リストのサイズが非常に小さい場合はメソッドの方が優れていますが、リストのサイズが大きい場合はジェネレータ式の方が適しています。
一方、検索がハイブリッド式とジェネレータ式の最初から始まるので、共有要素が体系的に配列の最後にある場合(または両方のリストが値を共有しない場合)は、分離および集合交差のアプローチは次のようになります。ジェネレータ式とハイブリッドアプローチよりもはるかに高速です。
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668
リストのサイズが大きくなると、ジェネレーターの式が遅くなることに注意してください。これは、前の図の100000ではなく、1000回の繰り返しの場合のみです。この設定は、要素が共有されていない場合にもよく似ており、ばらばらにして交差を設定するアプローチに最適です。
乱数を使用した2つの分析を以下に示します(セットアップをリギングして、いずれかの手法を支持する代わりに)。
共有の可能性が高い:要素はからランダムに取得され[1, 2*len(a)]
ます。共有の可能性が低い:要素はからランダムに取得され[1, 1000*len(a)]
ます。
これまで、この分析では両方のリストが同じサイズであると想定していました。たとえば、サイズが異なる2つのリストの場合a
は、はるかに小さく、isdisjoint()
常に高速です。
a
リストが小さいことを確認してください。小さい場合、パフォーマンスが低下します。この実験では、a
リストのサイズを一定に設定しました5
。
要約すれば:
- リストが非常に小さい場合(<10要素)、
not set(a).isdisjoint(b)
常に最速です。
- リスト内の要素が並べ替えられているか、利用できる通常の構造である場合、ジェネレーター式
any(i in a for i in b)
は、リストのサイズが大きい場合に最速になります。
- との交点をテストします。
not set(a).isdisjoint(b)
これは常により高速ですbool(set(a) & set(b))
。
- ハイブリッドの「リストの反復、セットでのテスト」
a = set(a); any(i in a for i in b)
は、他の方法よりも一般的に低速です。
- ジェネレータ式とハイブリッドは、要素を共有しないリストに関しては、他の2つのアプローチよりもはるかに低速です。
isdisjoint()
要素が共有されていない場合は非常に非効率的であるため、ジェネレータ式の実行にははるかに長い時間がかかるため、ほとんどの場合、メソッドを使用するのが最良のアプローチです。
len(...) > 0
ためにドロップすることbool(set([]))
です。もちろん、リストを最初からセットとして保持している場合は、セット作成のオーバーヘッドを節約できます。