リストがPythonでアイテムを共有しているかどうかをテストする


131

あるリストのアイテムが別のリストに存在するかどうを確認したいと思います。以下のコードで簡単にできますが、これを行うためのライブラリー関数があるのではないかと思います。そうでない場合、同じ結果を達成するためのよりパイソン的な方法がありますか?

In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

私が考えることができる唯一の最適化は、Falseをもたらすlen(...) > 0ためにドロップすることbool(set([]))です。もちろん、リストを最初からセットとして保持している場合は、セット作成のオーバーヘッドを節約できます。
msw


1
注あなたが明確なことができないことTrueから1してFalseから0not set([1]).isdisjoint([True])取得True他のソリューションと同じ、。
ディマリ2017

回答:


313

短い答え:を使用しますnot set(a).isdisjoint(b)。これが一般的に最速です。

2つのリストをテストし、アイテムab共有する一般的な方法は4つあります。最初のオプションは、両方をセットに変換し、それらの交差をチェックすることです。

bool(set(a) & set(b))

セットはPythonのハッシュテーブルを使用して保存されるO(1)ため、検索はそうです(Pythonの演算子の複雑さについては、こちらを参照してください)。理論的には、これがあるO(n+m)ために、平均してnおよびmリスト内のオブジェクトab。ただし、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()常に高速です。

要素が最初に共有されたときの2つの異なるサイズのリストでのテスト実行時間の共有 最後に共有されたときの2つの異なるサイズのリストでの要素共有テスト実行時間

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()要素が共有されていない場合は非常に非効率的であるため、ジェネレータ式の実行にははるかに長い時間がかかるため、ほとんどの場合、メソッドを使用するのが最良のアプローチです。


8
これはいくつかの有用なデータであり、Big-O分析が実行時間に関するすべての推論を終わらせるわけではないことを示しています。
スティーブアリソン

最悪のシナリオはどうですか?any最初の非False値で終了します。一致する値が最後にあるリストを使用すると、次のようになります 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 。...繰り返し回数は1000回のみです。
RobM

2
情報をありがとう@RobM。これを反映し、このスレッドで提案されている他の手法を考慮に入れるように、回答を更新しました。
Soravux 2016年

not set(a).isdisjoint(b)2つのリストがメンバーを共有しているかどうかをテストする必要があります。2つのリストがメンバーを共有していない場合にset(a).isdisjoint(b)返されます。答えは編集する必要がありますか?True
Guillochon

1
頭を上げてくれてありがとう、@ Guillochon、修正されました。
Soravux 2017

25
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

注:上記では、ブール値を答えにしたいと想定しています。ifステートメントで使用する式だけが必要な場合は、if set(a) & set(b):


5
これは最悪の場合のO(n + m)です。ただし、欠点は、新しいセットが作成され、共通の要素が早期に見つかったときに救済されないことです。
Matthew Flaschen、

1
これがなぜなのか興味がありO(n + m)ます。私の推測では、セットはハッシュテーブルを使用して実装されているため、in演算子はO(1)時間内に動作できます(縮退した場合を除く)。これは正しいです?もしそうなら、ハッシュテーブルのワーストケースルックアップパフォーマンスがO(n)であることを考えると、これは、最悪の場合とは異なり、パフォーマンスが向上することを意味しO(n * m)ますか?
fmark 10/07/03

1
@fmark:理論的には、あなたは正しい。実際には、誰も気にしません。CPythonソースのObjects / dictobject.cのコメントを読み(セットはキーのみのdictsであり、値はありません)、O(n)ルックアップパフォーマンスを引き起こすキーのリストを生成できるかどうかを確認します。
John Machin、2010

わかりました、説明してくれてありがとう、何か魔法が起こっているのではないかと思っていました:)。私は実質的に、私は気にする必要はありません、原因となりますキーのリストを生成することが自明であることに同意しますがO(n)、ルックアップのパフォーマンスを;)、参照pastebin.com/Kn3kAW7u LAFSのためだけに。
fmark 10/07/03

2
ええ、私は知っています。さらに、私があなたが指摘したソースを読んだだけです。これは、ランダムでないハッシュ関数(組み込み関数など)の場合のさらに多くの魔法を文書化しています。私はそれがJavaのようなランダムさを必要とし、それがこのstackoverflow.com/questions/2634690/…のような怪物をもたらすと想定しました。PythonはJavaではないことを忘れないでください(神に感謝します!)。
fmark

10
def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

これは漸近的に最適であり(最悪の場合O(n + m))、のany短絡のため、交差アプローチよりも優れている可能性があります。

例えば:

lists_overlap([3,4,5], [1,2,3])

到達するとすぐにTrueを返します 3 in sb

編集:別のバリエーション(Dave Kirbyのおかげで):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

これはimap、ジェネレーター内包ではなく、Cで実装されているのイテレーターに依存しています。sb.__contains__マッピング機能としても使用します。これがパフォーマンスにどの程度の違いをもたらすかはわかりません。それでも短絡します。


1
交差アプローチのループはすべてCコードです。あなたのアプローチには、Pythonコードを含む1つのループがあります。大きな未知は、交差点が空である可能性が高いかどうかです。
John Machin、2010

2
any(itertools.imap(sb.__contains__, a))ラムダ関数の使用を避けるため、どちらがより高速であるかを使用することもできます。
デイブカービー

ありがとう、@ Dave。:)私はラムダを削除することは勝利であることに同意します。
Matthew Flaschen

4

anyリスト内包表記でも使用できます。

any([item in a for item in b])

6
可能ですが、時間はO(n * m)ですが、集合交差アプローチの時間はO(n + m)です。リストを理解せずに(を失う[])、それを実行することもでき、より速く実行され、より少ないメモリを使用しますが、時間はO(n * m)のままです。
John Machin、2010

1
あなたのビッグO分析は真実ですが、nとmの値が小さい場合、基礎となるハッシュテーブルを構築するのにかかる時間が関係してくると思います。Big Oは、ハッシュの計算にかかる時間を無視します。
Anthony Conyers

2
「ハッシュテーブル」の構築は、償却されたO(n)です。
John Machin、2010

1
わかりましたが、あなたが捨てている定数はかなり大きいです。nの値が大きい場合は問題ありませんが、小さい場合は問題になります。
Anthony Conyers

3

Python 2.6以降では、次のことができます。

return not frozenset(a).isdisjoint(frozenset(b))

1
最初の引数としてセットやフローズンセットを提供する必要がないようです。私は文字列を試してみましたが、うまくいきました(つまり、任意のイテラブルで可能です)。
アクタウ2015

2

任意の組み込み関数/ waジェネレーター式を使用できます。

def list_overlap(a,b): 
     return any(i for i in a if i in b)

JohnとLieが指摘したように、2つのリストによって共有されるすべてのiについて、bool(i)== Falseの場合、これは誤った結果をもたらします。そのはず:

return any(i in b for i in a)

1
リーライアンのコメントを増幅する:bool(x)がFalse である交差点にあるアイテムxに対して誤った結果を返します。リーライアンの例では、xは0です。修正はany(True for i in a if i in b)、すでに見たとおりに記述されている方が適切ですany(i in b for i in a)
John Machin、2010

1
訂正:とき、間違った結果が得られますすべてのアイテムxの交差点では、そのようなものです。bool(x)False
John Machin、2010

1

この質問はかなり古いですが、人々はセット対リストについて議論している間、誰もそれらを一緒に使用することを考えていなかったことに気付きました。Soravuxの例に従って、

リストの最悪のケース:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

そして、リストの最良のケース:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

したがって、2つのリストを繰り返し処理するよりも高速なのは、リストに含まれているかどうかを確認するためにリストを繰り返し処理することです。これは、数値がセット内にあるかどうかを確認するのに一定の時間がかかり、リストを繰り返し処理して確認するのに時間がかかるためです。リスト。

したがって、私の結論は、リスト反復処理し、それがセット内にあるかどうかを確認することです。


1
使用isdisjoint()@Toughyによって示されるように(凍結)セットに方法はさらに良好である:timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)=> 0.00913715362548828
アクタウ

1

重複する要素が何であるかを気にしない場合lenは、結合されたリストとセットとして結合されたリストを単純にチェックできます。重複する要素がある場合、セットは短くなります。

len(set(a+b+c))==len(a+b+c) 重複がない場合はTrueを返します。


最初の値が重複している場合でも、リストの大きさがどれほど大きくても、リスト全体がセットに変換されます。
Peter Wood

1

関数型プログラミングスタイルで別のものを投入します。

any(map(lambda x: x in a, b))

説明:

map(lambda x: x in a, b)

で要素bが見つかったブール値のリストを返しますa。次に、そのリストがに渡されanyます。Trueこれは、要素がある場合にのみ戻りますTrue

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.