web-dev-qa-db-ja.com

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

あるリストの項目のanyが別のリストに存在するかどうかを確認したい。以下のコードで簡単にできますが、これを行うためのライブラリ関数があるかもしれません。そうでない場合、同じ結果を達成するためのよりPython的な方法があります。

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
   ....: 
110
fmark

短い答えnot set(a).isdisjoint(b)を使用してください。これは一般的に最速です。

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

_bool(set(a) & set(b))
_

setsはPythonのハッシュテーブルを使用して保存されるため、それらの検索はO(1)here を参照) Pythonの演算子の複雑さに関する詳細情報)。理論的には、これはリストnおよびmaおよびbオブジェクトの平均O(n+m)です。ただし、1)リストから最初にセットを作成する必要があり、無視できない時間を要することがあり、2)ハッシュ衝突がデータ間でまばらであることを想定しています。

2番目の方法は、リストで反復を実行するジェネレーター式を使用することです。

_any(i in a for i in b)
_

これにより、インプレースで検索できるため、中間変数に新しいメモリは割り当てられません。また、最初の検索で解決します。 ただし、リストのin演算子は常にO(n)ですhere を参照)。

別の提案されたオプションは、リストの1つを反復処理し、セット内のもう1つを変換し、このセットのメンバーシップをテストするハイブリッドです。

_a = set(a); any(i in a for i in b)
_

4番目のアプローチは、(凍結)セットのisdisjoint()メソッドを利用することです( here を参照)。例:

_not set(a).isdisjoint(b)
_

検索する要素が配列の先頭近くにある場合(たとえば、並べ替えられている場合)、sets交差メソッドでは中間変数に新しいメモリを割り当てる必要があるため、ジェネレーター式が優先されます。

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

リストサイズの関数におけるこの例の実行時間のグラフは次のとおりです。

Element sharing test execution time when shared at the beginning

両方の軸が対数であることに注意してください。これは、ジェネレータ式の最良のケースを表しています。ご覧のように、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
_

Element sharing test execution time when shared at the end

リストのサイズが大きくなると、ジェネレータ式がかなり遅くなることに注意してください。これは、前の図の100000回ではなく、1000回の繰り返しのみです。この設定は、要素が共有されていない場合にも近似しており、互いに素なアプローチおよび集合交差アプローチの最良のケースです。

乱数を使用した2つの分析を次に示します(セットアップをリギングして、ある手法または別の手法を優先する代わりに)。

Element sharing test execution time for randomly generated data with high chance of sharingElement sharing test execution time for randomly generated data with high chance of sharing

共有の可能性が高い:要素は[1, 2*len(a)]からランダムに取得されます。共有の可能性が低い:要素は[1, 1000*len(a)]からランダムに取得されます。

これまで、この分析では、両方のリストが同じサイズであると想定していました。サイズの異なる2つのリストの場合、たとえばaはずっと小さく、isdisjoint()は常に高速です。

Element sharing test execution time on two differently sized lists when shared at the beginningElement sharing test execution time on two differently sized lists when shared at the end

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

263
Soravux
_def lists_overlap3(a, b):
    return bool(set(a) & set(b))
_

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

24
John Machin
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])

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

編集:別のバリエーション(デイブカービーのおかげで):

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

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

10

リスト内包表記でanyを使用することもできます:

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

python 2.6以降では次のことができます:

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

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

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)
2
Anthony Conyers

関数型プログラミングスタイルでもう1つをスローします。

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

説明:

map(lambda x: x in a, b)

bの要素がaにあるブール値のリストを返します。次に、そのリストはanyに渡され、要素がTrueである場合、単純にTrueを返します。

1
cs01

この質問はかなり古いですが、人々がセットとリストを議論している間、誰もそれらを一緒に使用することを考えていないことに気付きました。 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
binoche9

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

len(set(a+b+c))==len(a+b+c)は、オーバーラップがない場合にTrueを返します。

1
domoarigato