Pythonリスト減算操作


227

私はこれに似た何かをしたいです:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

しかし、これはpythonリストではサポートされていません。それを行うための最良の方法は何ですか?


@ezdazuzenaこれは減算ではありません。これが2つのリストの違いです。あなたの共有はこの質問の重複ではありません。
Celik 2016年

1
[2、2]-[2]は何を返すべきですか?[]?[2]?
マッケイ

@McKay [2,2]-[2]は[2]を返す必要があります。[2,2] - [1,2,2,3]は返さなければならない[]
Robino

この質問はリストの減算についてですが、受け入れられた答えは集合減算に近いです。
ロビノ2017

2
[2、1、2、3、2、4、2]-[2、3、2]は何を返す必要がありますか。その理由は何ですか。途中で232を見つけて2142を返す必要がありますか?または毎回最初を見つけて1242を返す必要がありますか?または、他の何か?私が言っているのは、これらは明白な答えではなく、必要性に依存しているということです。
マッケイ2017

回答:


330

リスト内包表記を使用します。

[item for item in x if item not in y]

-中置構文を使用したい場合は、次のようにします。

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

その後、次のように使用できます。

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

ただし、リストのプロパティ(順序など)が絶対に必要でない場合は、他の回答が推奨するように、セットを使用してください。


10
@admica、コンストラクタをlistシャドウするため、変数名には使用しないでくださいlist。「リスト」を使用する場合は、その前にアンダースコアを付けてください。また、をドロップすると*、私のコードが壊れました...
aaronasterling

19
あなたが行う場合[1,1,2,2] - [1,2]、あなたは空のリストを取得します。[1,1,2,2] - [2]与え[1,1]、それが本当にリストsubstractionではありませんので、それはより多くのようなものです「一覧から一覧Xセットからの要素のないY
Alfred Zien

彼が言ったこと@AlfredZien
RetroCode

リスト内包表記法は、(この例では)集合差分法よりもかなり低速です。
redfiloux

1
@BarnabasSzabolcs:毎回のチェックの前に変換さyれるため(元の作業と同様のコストです)、それは事を節約しません。あなたは何のいずれかに必要があると思いますテスト、その後、listcomp外で行う、または悪質なハックとしてキャッシュするためにどの虐待、ネストされたlistcompsをワンライナーとして。への引数は1回しか作成されないため、適切に実行される少し見苦しくないワンライナーソリューションを使用することになります。setyset = set(y)if item not in yset[item for yset in [set(y)] for item in x if item not in yset]ysetlist(itertools.filterfalse(set(y).__contains__, x))filterfalse
ShadowRanger

259

セット差を使用

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

または、xとyをセットにするだけで、変換を行う必要がない場合もあります。


50
これは順序を失います。これは、状況に応じて重要な場合と重要でない場合があります。
aaronasterling 2010

63
これにより、維持が必要または必要になる可能性のあるすべての重複も失われます。
オパール

取得しますTypeError: unhashable type: 'dict'
Havnar 2017

これは、リストが比較されている場合にはより高速な方法である大きい
JqueryToAddNumbers

2
リスト内のアイテムの順序と複製がコンテキストにとって重要ではない場合、これは優れた回答であり、非常に読みやすいです。
ワットイアムスリ

37

これが「セット減算」演算です。そのためにセットデータ構造を使用します。

Python 2.7の場合:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

出力:

>>> print x - y
set([0, 8, 2, 4, 6])

1
list(set([1,2,3,4,5])-set([1,2,3]))= [4、5]これにより、それぞれが最初に設定し、次に減算(または一方向差分)するリストになります)とリストに戻ります。
gseattle

2
xセットの元のアイテムの順序を維持したい場合は好ましくありません。
Zahran 2017

34

アイテムの重複と注文に問題がある場合:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

2
これは動作しますが、O(m * n)実行時です(そして、listcompに副作用が含まれるときはいつでも私はうんざりします)。ランタイムを取得するために使用collections.Counterして、それを改善できO(m + n)ます。
ShadowRanger

私はこれを理解するのに苦労しています、誰かが説明できますか?
anushka

20

多くのユースケースで必要な答えは次のとおりです。

ys = set(y)
[item for item in x if item not in ys]

これはaaronasterlingの回答quantumSoupの回答ハイブリッドです。

aaronasterlingのバージョンはlen(y)、の各要素のアイテム比較を行うxため、2次時間がかかります。quantumSoupのバージョンが使用するセット、それは内の各要素のための単一の一定時間設定のルックアップして、xそれが変換されるため、ブタの両方を xyセットに、それはあなたの要素の順序を失います。

yセットのみに変換し、x順番に反復することにより、両方の世界の最高の線形時間と順序保存が得られます。*


ただし、quantumSoupのバージョンにはまだ問題があります。要素をハッシュ可能にする必要があります。それはセットの性質にかなり組み込まれています。**たとえば、別の辞書リストから辞書リストを減算しようとしているが、減算するリストが大きい場合、どうしますか?

ハッシュ可能になるように値を装飾できる場合は、問題が解決します。たとえば、値自体がハッシュ可能であるフラットディクショナリの場合:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

型がもう少し複雑な場合(たとえば、ハッシュ可能なJSON互換の値、または値が再帰的に同じ型であるリストまたはディクショナリを扱う場合が多い)でも、このソリューションを使用できます。ただし、一部のタイプはハッシュ可能なものに変換できません。


アイテムがハッシュ可能ではなく、ハッシュ可能でも作成できない場合は、比較可能であれば、少なくとも対数線形時間を取得できます(O(N*log M)これはO(N*M)、リストソリューションの時間よりもはるかに優れていますが、O(N+M)ソートや使用して設定されたソリューションの時間)bisect

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

アイテムがハッシュ可能でも比較可能でもない場合は、2次解で立ち往生しています。


* OrderedSetオブジェクトのペアを使用してこれを行うこともできます。オブジェクトのペアには、レシピとサードパーティのモジュールが含まれています。しかし、これはもっと簡単だと思います。

**セットの検索が一定の時間で行われる理由は、値をハッシュしてそのハッシュのエントリがあるかどうかを確認するだけでよいためです。値をハッシュできない場合、これは機能しません。


7

セットでの値の検索は、リストでの値の検索よりも高速です。

[item for item in x if item not in set(y)]

これは次の場合よりもわずかに優れていると思います。

[item for item in x if item not in y]

どちらもリストの順序を保持します。


それはキャッシュしset(y)y各ループで新しいセットに変換しませんか?それ以外の場合は、禁忌の答えが必要ですys = set(y); [i for i in x if i not in ys]
ジャックトース

2
いくつかの大まかなテストでは、(リストは)if i not in set(y)より25%長くかかることが示唆されています。セットの事前変換にかかる時間は55%少なくなります。かなり短く、でテストされていますが、長さがあると、違いがより顕著になります。if i not in yyxy
ジャックトース

1
@Jacktose:それは反復処理し、ハッシュしなければならないのでうん、このソリューションは、より多くの作業を行うすべてのの要素yのためのすべてのの要素をx。等価性の比較がハッシュ計算に比べて非常に高価でない限り、これは常にplainに負けitem not in yます。
ShadowRanger

意味のある@ShadowRanger。セット変換がそのチェックを確実に迅速に行う方法である場合、コンパイラーは常にその方法でチェックを行うと思います。
Jacktose

5

リストで重複する要素が許可されている場合は、コレクションのCounterを使用できます。

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())

xの要素の順序を保持する必要がある場合:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

順序は失われますが、これは良いことです。修正はもう少し複雑です。
ShadowRanger

@ShadowRanger、それは確かに。しかし、ほんの少し。
Alain T.

気にしないでください。キャッシュと副作用を伴うlistcompsに身震いするつもりです(ただし、この2つの組み合わせにより、外部から見える副作用はなくなると思いますか?)。:-)
ShadowRanger

また、このコードは記述どおりに機能しません。Counter.subtractはゼロ値の要素を削除しません(-そして削除しますが削除しませ-=subtract)ので、要素の削除を停止することは決してありません。置換not v in cしたいnot c[v](存在しない要素に対してゼロを返すので、を使用して「ゼロ性」の戻りを安全にテストできますnot)。
ShadowRanger

@ShadowRanger、グッドキャッチ!今それを修正しました。
Alain T.

3

他のソリューションには、いくつかの問題の1つがあります。

  1. 彼らは秩序を守らない、または
  2. これらは、要素の正確な数を削除しません。たとえばx = [1, 2, 2, 2]y = [2, 2]に変換yし、に変換します。適切な動作が2回削除される場合は、set一致するすべての要素を削除する(残す[1]のみ)か、各一意の要素の1つを削除する(を残す)か、出発、または[1, 2, 2]2[1, 2]
  3. 彼らはやるO(m * n)最適解を行うことができます仕事、O(m + n)仕事を

アランはCounter#2と#3を解決するために正しい方向に進んでいましたが、その解決策は順序を失います。順序を維持するソリューション(削除するn値のn繰り返しで各値の最初のコピーlistを削除する)は次のとおりです。

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

オンラインでお試しください!

各要素の最後のコピーを削除するには、forループをに変更し、ループを終了した直後にfor val in reversed(x):追加out.reverse()forます。

構築CounterO(n)の面でyの長さは、反復がxあるO(n)という点でx長さs 'は、およびCounterメンバーシップのテストと変異があるO(1)一方で、list.append償却されてO(1)与えられた(appendすることができO(n)、多くのためappendの、総合的なビッグOの平均値O(1)少なくなりため、それらのうちの再割り当てが必要なため)、全体的な作業はO(m + n)です。

をテストして、テストによってy削除されなかった要素があったかどうかを確認することもできxます。

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

注:これ値がハッシュ可能である必要がありますが、ハッシュ可能オブジェクトを必要としないソリューションは、汎用ではない(たとえばint、固定長配列にsをカウントできる)か、機能以上のものを実行する必要がありO(m + n)ます(たとえば、次善の大きさ) -Oはlist、一意の値/カウントのペアを並べ替えて、O(1) dictルックアップをO(log n)バイナリ検索に変更します。並べ替えられた非一意の値だけでなく、カウントを含む一意の値が必要になります。それ以外の場合はO(n)、削除するためのコストがかかるためです。ソート済みの要素list)。
ShadowRanger

2

これを試して。

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>

2

これを実現する最も簡単な方法は、set()を使用することだと思います。

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> y = [1,3,5,7,9]  
>>> list(set(x)- set(y))
[0, 2, 4, 6, 8]

1

@aaronasterlingが提供する答えは良さそうですが、リストのデフォルトのインターフェースx = MyList(1, 2, 3, 4)vs とは互換性がありませんx = MyList([1, 2, 3, 4])。したがって、以下のコードは、よりpythonリストに適したものとして使用できます。

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

例:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y

0

私はこれがより速いと思います:

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

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}

これは減算ではありません。実際、これは2つのリストの対称的な違いです。
Parth Chauhan

さらに、これはリスト内のハッシュ可能なオブジェクトでのみ機能します
zhukovgreen

-1

この例では、2つのリストを差し引きます。

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))

8
これは避けてください、それはO(N ^ 2)です
アレクサンダー-モニカを2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.