Python、リストの違いを計算する


195

Pythonでは、2つのリストの違いを計算する最良の方法は何ですか?

A = [1,2,3,4]
B = [2,5]

A - B = [1,3,4]
B - A = [5]

回答:


206

setアイテムの順序や繰り返しを気にしない場合に使用します。次の場合は、リスト内包表記を使用します。

>>> def diff(first, second):
        second = set(second)
        return [item for item in first if item not in second]

>>> diff(A, B)
[1, 3, 4]
>>> diff(B, A)
[5]
>>> 

31
set(b)アルゴリズムがTheta(n ^ 2)ではなくO(nlogn)であることを確認するために使用することを検討してください
Neil G

8
@Pencilcheck-Aでの順序付けや複製を気にしない場合。Bへの適用setは無害ですがA、元の代わりに適用して結果を使用することは無害Aです。
マークリード

1
@NeilGセットを構築するために費やされた時間を考慮しますか?私の場合(両方のリストには約10Mの文字列があります)、2つのセットを作成してそれらを減算する時間は、1つのセットを作成してリストを繰り返すよりもかなり長くなります。
dimril

@dimrilあなたがやりたいことが多ければ、もっと洗練されたものを実装すべきでしょう。たとえば、両方のリストO(n log n + m log m)をソートしてから、2番目のリストを反復処理しますが、バイナリ検索を使用して、最初のリスト内のアイテムを見つけます。これは、O(n * m)演算ではなく、O(n log n + m log m + m log n)演算に出てきますが、それほど悪くないようです。隣人をチェックして、バイナリサーチ実装の重複を排除してください。すでにこれを実装しているパッケージがあるかもしれませんが、私はチェックしませんでした。
jaaq

365

順序が重要でない場合は、単にセットの差を計算できます。

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

9
これは断然最良の解決策です。それぞれ〜6000個の文字列を含むリストのテストケースでは、この方法がリスト内包表記よりもほぼ100倍高速であることが示されました。
perrygeo 2014

15
アプリケーションによって異なります。順序または複製の保存が重要な場合は、Roman Bodnarchukがより良いアプローチをとることがあります。速度と純粋なセットのような動作の場合、これはより良いようです。
ブライアンP

7
リストに複数の等しい要素がある場合、このソリューションは機能しません。
karantan 2016

リストの理解よりはるかに優れています。
Dawei

4
この解決策は明白なようですが、それは正しくありません。申し訳ありません。もちろん、リストは同じ要素を繰り返し持つことができることを意味します。それ以外の場合は、リストの違いではなく、セット間の違いについて尋ねます。
sergzach

67

あなたはできる

list(set(A)-set(B))

そして

list(set(B)-set(A))

7
しかし、A = [1,1,1]およびB = [0]の場合、これは[1]を返します
Mark Bell

1
@Mark Bell:セットは明確なリストだからです。(重複を削除)
曇り

1
@cloudyそれではこれは質問に答えません。
samm82

@ samm82 set(A)よりA = [1,1,1]の場合、[1]は[1]です。これは、セットが個別のリストであり、重複を削除するためです。そのため、A = [1,1,1]およびB = [0]の場合、[1]を返します。
曇り

29

一発ギャグ:

diff = lambda l1,l2: [x for x in l1 if x not in l2]
diff(A,B)
diff(B,A)

または:

diff = lambda l1,l2: filter(lambda x: x not in l2, l1)
diff(A,B)
diff(B,A)

14

上記の例は、差を計算する問題を簡単にしました。並べ替えまたは重複除外を使用すると、間違いなく差を計算しやすくなりますが、比較でこれらの仮定ができない場合は、差分アルゴリズムの重要な実装が必要になります。Python標準ライブラリのdifflibを参照してください。

from difflib import SequenceMatcher 

squeeze=SequenceMatcher( None, A, B )

print "A - B = [%s]"%( reduce( lambda p,q: p+q, 
                               map( lambda t: squeeze.a[t[1]:t[2]], 
                                    filter(lambda x:x[0]!='equal', 
                                           squeeze.get_opcodes() ) ) ) )

A-B = [[1、3、4]]


1
これまでに見たことのないdifflibの+1を取得します。それにもかかわらず、私は上記の答えが述べられたように問題簡単にすることには同意しません。
rbp 2013年

difflibをご利用いただきありがとうございます-標準ライブラリを使用した解決策を探していました。しかし、これは、Python 3で動作していない、などprintの機能へのコマンドから変更されている、とreducefiltermapunpythonic宣言されています。(そして、私はグイドが正しいかもしれないと思います-私も何をするのか分かりませんreduce。)
Post169

py3で動作させるための大きなシフトではありません。私はフィルター、マップ、リデュースについての議論を読み、フィルターのインプリットとリデュースと代替の実装をfunctoolsにプッシュするという選択に同意しました。Pythonの機能、オブジェクト指向、手続き型の性質が混在しているのは、IMOの強みの1つです。
ケビン

14

Python 2.7.3(デフォルト、2014年2月27日19:58:35)-IPython 1.1.0-timeit:(github gist

def diff(a, b):
  b = set(b)
  return [aa for aa in a if aa not in b]

def set_diff(a, b):
  return list(set(a) - set(b))

diff_lamb_hension = lambda l1,l2: [x for x in l1 if x not in l2]

diff_lamb_filter = lambda l1,l2: filter(lambda x: x not in l2, l1)

from difflib import SequenceMatcher
def squeezer(a, b):
  squeeze = SequenceMatcher(None, a, b)
  return reduce(lambda p,q: p+q, map(
    lambda t: squeeze.a[t[1]:t[2]],
      filter(lambda x:x[0]!='equal',
        squeeze.get_opcodes())))

結果:

# Small
a = range(10)
b = range(10/2)

timeit[diff(a, b)]
100000 loops, best of 3: 1.97 µs per loop

timeit[set_diff(a, b)]
100000 loops, best of 3: 2.71 µs per loop

timeit[diff_lamb_hension(a, b)]
100000 loops, best of 3: 2.1 µs per loop

timeit[diff_lamb_filter(a, b)]
100000 loops, best of 3: 3.58 µs per loop

timeit[squeezer(a, b)]
10000 loops, best of 3: 36 µs per loop

# Medium
a = range(10**4)
b = range(10**4/2)

timeit[diff(a, b)]
1000 loops, best of 3: 1.17 ms per loop

timeit[set_diff(a, b)]
1000 loops, best of 3: 1.27 ms per loop

timeit[diff_lamb_hension(a, b)]
1 loops, best of 3: 736 ms per loop

timeit[diff_lamb_filter(a, b)]
1 loops, best of 3: 732 ms per loop

timeit[squeezer(a, b)]
100 loops, best of 3: 12.8 ms per loop

# Big
a = xrange(10**7)
b = xrange(10**7/2)

timeit[diff(a, b)]
1 loops, best of 3: 1.74 s per loop

timeit[set_diff(a, b)]
1 loops, best of 3: 2.57 s per loop

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# TypeError: sequence index must be integer, not 'slice'

@ roman-bodnarchukリスト内包表記の関数def diff(a、b)の方が高速のようです。




5

違いを再帰的にリストの項目に深く入れたい場合は、Python用のパッケージを作成しました。 https //github.com/erasmose/deepdiff

取り付け

PyPiからインストール:

pip install deepdiff

Python3の場合は、次もインストールする必要があります。

pip install future six

使用例

>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function

同じオブジェクトが空を返す

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {}

アイテムの種類が変更されました

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'type_changes': ["root[2]: 2=<type 'int'> vs. 2=<type 'str'>"]}

アイテムの値が変更されました

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'values_changed': ['root[2]: 2 ====>> 4']}

追加または削除されたアイテム

>>> t1 = {1:1, 2:2, 3:3, 4:4}
>>> t2 = {1:1, 2:4, 3:3, 5:5, 6:6}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes)
    {'dic_item_added': ['root[5, 6]'],
     'dic_item_removed': ['root[4]'],
     'values_changed': ['root[2]: 2 ====>> 4']}

文字列の違い

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world"}}
>>> t2 = {1:1, 2:4, 3:3, 4:{"a":"hello", "b":"world!"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ 'root[2]: 2 ====>> 4',
                          "root[4]['b']:\n--- \n+++ \n@@ -1 +1 @@\n-world\n+world!"]}
>>>
>>> print (ddiff.changes['values_changed'][1])
    root[4]['b']:
    --- 
    +++ 
    @@ -1 +1 @@
    -world
    +world!

文字列の違い2

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world!\nGoodbye!\n1\n2\nEnd"}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n1\n2\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ "root[4]['b']:\n--- \n+++ \n@@ -1,5 +1,4 @@\n-world!\n-Goodbye!\n+world\n 1\n 2\n End"]}
>>>
>>> print (ddiff.changes['values_changed'][0])
    root[4]['b']:
    --- 
    +++ 
    @@ -1,5 +1,4 @@
    -world!
    -Goodbye!
    +world
     1
     2
     End

タイプ変更

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n\n\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'type_changes': [ "root[4]['b']: [1, 2, 3]=<type 'list'> vs. world\n\n\nEnd=<type 'str'>"]}

リストの違い

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'list_removed': ["root[4]['b']: [3]"]}

リストの違い2:順序は考慮されないことに注意してください

>>> # Note that it DOES NOT take order into account
... t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { }

辞書を含むリスト:

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:1, 2:2}]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:3}]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'dic_item_removed': ["root[4]['b'][2][2]"],
      'values_changed': ["root[4]['b'][2][1]: 1 ====>> 3"]}


2

辞書のリストの場合、完全なリストの理解ソリューションは機能しますが、setソリューションは

TypeError: unhashable type: 'dict'

テストケース

def diff(a, b):
    return [aa for aa in a if aa not in b]

d1 = {"a":1, "b":1}
d2 = {"a":2, "b":2}
d3 = {"a":3, "b":3}

>>> diff([d1, d2, d3], [d2, d3])
[{'a': 1, 'b': 1}]
>>> diff([d1, d2, d3], [d1])
[{'a': 2, 'b': 2}, {'a': 3, 'b': 3}]

0

必要な場合に複数のアイテムとの違いを与える単純なコード:

a=[1,2,3,3,4]
b=[2,4]
tmp = copy.deepcopy(a)
for k in b:
    if k in tmp:
        tmp.remove(k)
print(tmp)

-1

In-operatorのTimeComplexityを見ると、最悪の場合、O(n)で動作します。セットでも。

したがって、2つの配列を比較する場合、TimeComplexityは、最良の場合はO(n)、最悪の場合はO(n ^ 2)になります。

最良かつ最悪の場合にO(n)で機能する代替(ただし、残念ながらより複雑な)ソリューションは次のとおりです。

# Compares the difference of list a and b
# uses a callback function to compare items
def diff(a, b, callback):
  a_missing_in_b = []
  ai = 0
  bi = 0

  a = sorted(a, callback)
  b = sorted(b, callback)

  while (ai < len(a)) and (bi < len(b)):

    cmp = callback(a[ai], b[bi])
    if cmp < 0:
      a_missing_in_b.append(a[ai])
      ai += 1
    elif cmp > 0:
      # Item b is missing in a
      bi += 1
    else:
      # a and b intersecting on this item
      ai += 1
      bi += 1

  # if a and b are not of same length, we need to add the remaining items
  for ai in xrange(ai, len(a)):
    a_missing_in_b.append(a[ai])


  return a_missing_in_b

例えば

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.