あるリストにあるすべての要素を別のリストから削除します


366

2つのリストがあるl1としましょうl2。not inのl1 - l2すべての要素を返す、実行したい。l1l2

これを行うための単純なループのアプローチを考えることができますが、それは本当に非効率的です。これを行うためのpythonicで効率的な方法は何ですか?

例として、私が持っている場合l1 = [1,2,6,8] and l2 = [2,3,5,8]l1 - l2返す必要があります[1,6]


12
ただヒント:PEP8が、それはあまりにも多くの1のように見えるので、その小文字の「L」を述べては使用すべきではない
spelchekr

2
同意する。私はこの質問全体とその答えを読み、なぜ11と12を使用し続けたのか疑問に思いました。私が@spelchekrのコメントを読んだときだけ、それは理にかなっています。
robline


@JimG。データフレームとリストは同じものではありません。
活動の削減

回答:


491

Pythonには、List Comprehensionsと呼ばれる言語機能があり、このようなことを非常に簡単にするのに最適です。次のステートメントは、必要な処理を正確に実行し、結果をに格納しますl3

l3 = [x for x in l1 if x not in l2]

l3含まれます[1, 6]


8
非常にpythonic。私はそれが好きです!どれくらい効率的ですか?
ファンダム2010年

2
私は非常に効率的だと思います。それはあなたが達成しようとしていることに関して非常に読みやすく、明確であるという利点があります。効率性に関して興味深いと思うブログ投稿を見つけました:blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Donut

6
@fandom:リスト内包自体は非常に効率的ですが(ジェネレーター内包はメモリ内の要素を複製しない方が効率的かもしれませんが)、in演算子はリスト上ではそれほど効率的ではありません。 inリストではO(n)、inセットではO(1)です。ただし、数千以上の要素に到達するまで、違いに気付くことはほとんどありません。
Daniel Pryden、2010年

1
l3 = [x for x in l1 if x not in set(l2)]set(l2)一度以上呼ばれると確信しています。
Danosaure、2010年

5
単に設定l2s = set(l2)してから発声することもできますl3 = [x for x in l1 if x not in l2s]。少し簡単です。
spelchekr 2015年

149

1つの方法は、セットを使用することです。

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
これによりl1、からの重複も削除されます。これは、望ましくない副作用となる場合があります。
キンドール

37
..そして要素の順序を失います(順序が重要な場合)。
Danosaure 2010年

3
私はこれを受け入れられた回答と比較して時間を計ったことを追加したいだけで、それは約3倍のパフォーマンスを発揮しましたtimeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969。したがって、パフォーマンスが重要な要素である場合、この回答の方が適切な場合があります(重複や順序を気にしない場合も同様)
wfgeo

38

別の方法として、ラムダ式使用filterて目的の結果を取得することもできます。例えば:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

性能比較

ここで、ここで述べたすべての回答のパフォーマンスを比較しています。予想通り、Arkkuの setベースの操作は最速です。

  • Arkkuのセットの違い -最初(ループあたり0.124 usec)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
  • Daniel Prydenのsetルックアップ付きリスト理解-2番目(ループあたり0.302 usec)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
  • プレーンリストでのドーナツのリストの理解 -3番目(ループあたり0.552 usec)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
  • Moinuddin Quadriの使用filter -4番目(ループあたり0.972 usec)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
  • Akshay Hazariのreduce+filter -5番目の組み合わせ(ループあたり3.97 usec)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop

PS: set順序を維持せず、重複する要素をリストから削除します。したがって、これらのいずれかが必要場合は、セット差分を使用しないでください。


32

ここでドーナツの答えと他の答えを拡張すると、リスト内包表記の代わりにジェネレータ内包表記を使用し、setデータ構造を使用することでさらに良い結果を得ることができます(in演算子はリストのO(n)ですがO(1)であるため)セット)。

だからここにあなたのために働く関数があります:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

結果は、フィルタリングされたリストを遅延フェッチする反復可能オブジェクトになります。実際のリストオブジェクトが必要な場合(たとえばlen()、結果に対してaを実行する必要がある場合)、次のようにリストを簡単に作成できます。

filtered_list = list(filter_list(full_list, excludes))

29

Pythonセットタイプを使用します。それが最もPythonicです。:)

また、ネイティブであるため、最も最適化された方法でもあります。

見る:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm(古いpythonの場合)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
セットを使用する場合、出力が順序付けられることに注意してください。つまり、{1,3,2}は{1,2,3}になり、{"A"、 "C"、 "B"}は{"A"、 "B"、 "C"}そして、あなたはそれを望まないかもしれません。
Pablo Reyes

2
リストl1に繰り返し要素が含まれている場合、このメソッドは機能しません。
jdhao 2018年

10

使用 セット内包 {xのxのL2}またはセット(L2)のセットを取得するために、次に使用リスト内包のリストを取得します

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

ベンチマークテストコード:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

ベンチマークテスト結果:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

1
l2set = set( l2 )代わりにl2set = { x for x in l2 }
CZ

1
素敵な魂!ただし、ハッシュ可能なオブジェクトでのみ機能することに注意してください。
Eerik Sven Puudist

7

代替ソリューション:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
この方法を使用する利点はありますか?それは多くの利点なしにそれはより複雑で読みにくいように見えます。
skrrgwasme 2015年

それは複雑に見えるかもしれません。削減は非常に柔軟であり、多くの目的に使用できます。それは折り目として知られています。reduceは実際にはfoldlです。より複雑なものを追加したい場合は、この関数で可能ですが、選択された最良の解答であるリスト内包表記では、同じタイプのリスト、つまりおそらく同じ長さの出力しか得られませんが、フォールドを使用すると、出力タイプも変更します。en.wikipedia.org/wiki/Fold_%28higher-order_function%29。このソリューションは、n * m以下の複雑さです。他の方が良いかもしれないしそうでないかもしれません。
Akshay Hazari 2015年

1
reduce(関数、リスト、初期アキュムレータ(任意のタイプにすることができます))
Akshay Hazari
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.