web-dev-qa-db-ja.com

2つのPython辞書に含まれるキーの差を計算します

2つのPython辞書-dictAdictBがあるとします。 dictBには存在するがdictAには存在しないキーがあるかどうかを調べる必要があります。それを行う最速の方法は何ですか?

辞書のキーをセットに変換してから実行する必要がありますか?

あなたの考えを知ることに興味があります...


ご回答ありがとうございます。

私の質問を適切に述べていないことをおologiesびします。私のシナリオはこのようなものです-dictAdictBと同じであるか、dictBと比較していくつかのキーが欠落している可能性があります。 dictAキーの値に設定する必要があります。

問題は、辞書に標準がなく、dict of dictになり得る値を持つことができることです。

いう

dictA={'key1':a, 'key2':b, 'key3':{'key11':cc, 'key12':dd}, 'key4':{'key111':{....}}}
dictB={'key1':a, 'key2:':newb, 'key3':{'key11':cc, 'key12':newdd, 'key13':ee}.......

したがって、「key2」値を新しい値にリセットし、「key13」を辞書内に追加する必要があります。キー値の形式は固定されていません。単純な値、dict、dict of dictのいずれかです。

161
Bhaskar

キーに対して集合演算を使用できます。

diff = set(dictb.keys()) - set(dicta.keys())

追加されたもの、削除されたもの、同じキーと値のペア、および変更されたキーと値のペア:すべての可能性を見つけるためのクラスを次に示します。

class DictDiffer(object):
    """
    Calculate the difference between two dictionaries as:
    (1) items added
    (2) items removed
    (3) keys same in both but changed values
    (4) keys same in both and unchanged values
    """
    def __init__(self, current_dict, past_dict):
        self.current_dict, self.past_dict = current_dict, past_dict
        self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys())
        self.intersect = self.set_current.intersection(self.set_past)
    def added(self):
        return self.set_current - self.intersect 
    def removed(self):
        return self.set_past - self.intersect 
    def changed(self):
        return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o])
    def unchanged(self):
        return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o])

出力例を次に示します。

>>> a = {'a': 1, 'b': 1, 'c': 0}
>>> b = {'a': 1, 'b': 2, 'd': 0}
>>> d = DictDiffer(b, a)
>>> print "Added:", d.added()
Added: set(['d'])
>>> print "Removed:", d.removed()
Removed: set(['c'])
>>> print "Changed:", d.changed()
Changed: set(['b'])
>>> print "Unchanged:", d.unchanged()
Unchanged: set(['a'])

Githubリポジトリとして利用可能: https://github.com/hughdbrown/dictdiffer

231
hughdbrown

再帰的に違いが必要な場合のために、Python用のパッケージを作成しました。 https://github.com/seperman/deepdiff

Installation

PyPiからインストールします。

pip install deepdiff

使用例

インポート中

>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function # In case running on Python 2

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

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

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

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{ 'type_changes': { 'root[2]': { 'newtype': <class 'str'>,
                                 'newvalue': '2',
                                 'oldtype': <class 'int'>,
                                 'oldvalue': 2}}}

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

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}

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

>>> 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)
{'dic_item_added': ['root[5]', 'root[6]'],
 'dic_item_removed': ['root[4]'],
 'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}

文字列の違い

>>> 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, indent = 2)
{ 'values_changed': { 'root[2]': {'newvalue': 4, 'oldvalue': 2},
                      "root[4]['b']": { 'newvalue': 'world!',
                                        'oldvalue': '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, indent = 2)
{ 'values_changed': { "root[4]['b']": { 'diff': '--- \n'
                                                '+++ \n'
                                                '@@ -1,5 +1,4 @@\n'
                                                '-world!\n'
                                                '-Goodbye!\n'
                                                '+world\n'
                                                ' 1\n'
                                                ' 2\n'
                                                ' End',
                                        'newvalue': 'world\n1\n2\nEnd',
                                        'oldvalue': 'world!\n'
                                                    'Goodbye!\n'
                                                    '1\n'
                                                    '2\n'
                                                    'End'}}}

>>> 
>>> print (ddiff['values_changed']["root[4]['b']"]["diff"])
--- 
+++ 
@@ -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, indent = 2)
{ 'type_changes': { "root[4]['b']": { 'newtype': <class 'str'>,
                                      'newvalue': 'world\n\n\nEnd',
                                      'oldtype': <class 'list'>,
                                      'oldvalue': [1, 2, 3]}}}

リストの違い

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

リストの違い2:

>>> 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, 3]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'iterable_item_added': {"root[4]['b'][3]": 3},
  'values_changed': { "root[4]['b'][1]": {'newvalue': 3, 'oldvalue': 2},
                      "root[4]['b'][2]": {'newvalue': 2, 'oldvalue': 3}}}

順序または重複を無視したリストの違い:(上記と同じ辞書を使用)

>>> 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, 3]}}
>>> ddiff = DeepDiff(t1, t2, ignore_order=True)
>>> print (ddiff)
{}

辞書を含むリスト:

>>> 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, indent = 2)
{ 'dic_item_removed': ["root[4]['b'][2][2]"],
  'values_changed': {"root[4]['b'][2][1]": {'newvalue': 3, 'oldvalue': 1}}}

セット:

>>> t1 = {1, 2, 8}
>>> t2 = {1, 2, 3, 5}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (DeepDiff(t1, t2))
{'set_item_added': ['root[3]', 'root[5]'], 'set_item_removed': ['root[8]']}

名前付きタプル:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> t1 = Point(x=11, y=22)
>>> t2 = Point(x=11, y=23)
>>> pprint (DeepDiff(t1, t2))
{'values_changed': {'root.y': {'newvalue': 23, 'oldvalue': 22}}}

カスタムオブジェクト:

>>> class ClassA(object):
...     a = 1
...     def __init__(self, b):
...         self.b = b
... 
>>> t1 = ClassA(1)
>>> t2 = ClassA(2)
>>> 
>>> pprint(DeepDiff(t1, t2))
{'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}

追加されたオブジェクト属性:

>>> t2.c = "new attribute"
>>> pprint(DeepDiff(t1, t2))
{'attribute_added': ['root.c'],
 'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}
53
Seperman

「高速」かどうかはわかりませんが、通常はこれを行うことができます

dicta = {"a":1,"b":2,"c":3,"d":4}
dictb = {"a":1,"d":2}
for key in dicta.keys():
    if not key in dictb:
        print key
18
ghostdog74

Alex Martelliが書いたように、単にBのキーがAにないかどうかを確認したい場合は、any(True for k in dictB if k not in dictA)が道です。

欠落しているキーを見つけるには:

diff = set(dictB)-set(dictA) #sets

C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA =    
dict(Zip(range(1000),range
(1000))); dictB = dict(Zip(range(0,2000,2),range(1000)))" "diff=set(dictB)-set(dictA)"
10000 loops, best of 3: 107 usec per loop

diff = [ k for k in dictB if k not in dictA ] #lc

C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA = 
dict(Zip(range(1000),range
(1000))); dictB = dict(Zip(range(0,2000,2),range(1000)))" "diff=[ k for k in dictB if
k not in dictA ]"
10000 loops, best of 3: 95.9 usec per loop

したがって、これら2つのソリューションはほぼ同じ速度です。

14
Jochen Ritzel

あなたが本当にあなたの言うことを本当に意味している場合(Bにあり、Aにはなく、キーがある場合にのみ見つける必要があることを意味します)、最速の方法は次のとおりです:

if any(True for k in dictB if k not in dictA): ...

実際にキーがある場合は、BではなくAで、「IF」だけでなくそのようなキーがあるかどうかを調べる必要がある場合、既存の答えは非常に適切です(ただし、確かにあなたの意味;-)。

12
Alex Martelli

set()を使用

set(dictA.keys()).intersection(dictB.keys())
7
Joziel Costa

hughdbrownによるトップアンサー は、間違いなく最良のアプローチである集合差を使用することを提案します:

diff = set(dictb.keys()) - set(dicta.keys())

このコードの問題は、2つのセットを作成するためだけに2つのリストを作成するため、4Nの時間と2Nのスペースが無駄になることです。また、必要以上に複雑です。

通常、これは大したことではありませんが、次の場合:

diff = dictb.keys() - dicta

Python 2

Python 2では、keys()は、KeysViewではなく、キーのリストを返します。したがって、viewkeys()を直接要求する必要があります。

diff = dictb.viewkeys() - dicta

デュアルバージョン2.7/3.xコードの場合は、sixまたは同様のコードを使用することが望ましいので、 six.viewkeys(dictb) を使用できます。

diff = six.viewkeys(dictb) - dicta

2.4-2.6では、KeysViewはありません。ただし、最初にリストを作成する代わりに、イテレータから直接左のセットを作成することで、少なくとも4NからNにコストを削減できます。

diff = set(dictb) - dicta

アイテム

私はdictBと同じであることができるdictAを持っているか、dictBと比較していくつかのキーが欠けているか、またはいくつかのキーの値が異なる可能性があります

したがって、キーを比較する必要はありませんが、アイテムを比較する必要があります。 ItemsViewは、値が文字列のようにハッシュ可能な場合にのみSetになります。もしそうなら、それは簡単です:

diff = dictb.items() - dicta.items()

再帰差分

質問は直接再帰的な差分を要求するものではありませんが、値の例のいくつかは辞書であり、予想される出力は再帰的に差分を表示するようです。その方法を示す複数の回答がすでにここにあります。

5
abarnert

この引数に関するstackoverflowの質問 があり、説明された簡単な解決策があることを認めなければなりません: datadiff library of python 2つの辞書の違い。

5
softwareplay

これは古い質問であり、私が必要としたものよりも少し少ない質問なので、この答えは実際にこの質問が尋ねるよりも多くを解決します。この質問の答えは、次の解決に役立ちました。

  1. (質問)2つの辞書の違いを記録する
  2. #1の違いをベース辞書にマージする
  3. (質問)2つの辞書の違いをマージします(辞書2を差分辞書であるかのように扱います)
  4. アイテムの動きと変更を検出してみてください
  5. (質問)このすべてを再帰的に行う

これらすべてをJSONと組み合わせると、非常に強力な構成ストレージのサポートが実現します。

解決策( githubでも ):

from collections import OrderedDict
from pprint import pprint


class izipDestinationMatching(object):
    __slots__ = ("attr", "value", "index")

    def __init__(self, attr, value, index):
        self.attr, self.value, self.index = attr, value, index

    def __repr__(self):
        return "izip_destination_matching: found match by '%s' = '%s' @ %d" % (self.attr, self.value, self.index)


def izip_destination(a, b, attrs, addMarker=True):
    """
    Returns zipped lists, but final size is equal to b with (if shorter) a padded with nulls
    Additionally also tries to find item reallocations by searching child dicts (if they are dicts) for attribute, listed in attrs)
    When addMarker == False (patching), final size will be the longer of a, b
    """
    for idx, item in enumerate(b):
        try:
            attr = next((x for x in attrs if x in item), None)  # See if the item has any of the ID attributes
            match, matchIdx = next(((orgItm, idx) for idx, orgItm in enumerate(a) if attr in orgItm and orgItm[attr] == item[attr]), (None, None)) if attr else (None, None)
            if match and matchIdx != idx and addMarker: item[izipDestinationMatching] = izipDestinationMatching(attr, item[attr], matchIdx)
        except:
            match = None
        yield (match if match else a[idx] if len(a) > idx else None), item
    if not addMarker and len(a) > len(b):
        for item in a[len(b) - len(a):]:
            yield item, item


def dictdiff(a, b, searchAttrs=[]):
    """
    returns a dictionary which represents difference from a to b
    the return dict is as short as possible:
      equal items are removed
      added / changed items are listed
      removed items are listed with value=None
    Also processes list values where the resulting list size will match that of b.
    It can also search said list items (that are dicts) for identity values to detect changed positions.
      In case such identity value is found, it is kept so that it can be re-found during the merge phase
    @param a: original dict
    @param b: new dict
    @param searchAttrs: list of strings (keys to search for in sub-dicts)
    @return: dict / list / whatever input is
    """
    if not (isinstance(a, dict) and isinstance(b, dict)):
        if isinstance(a, list) and isinstance(b, list):
            return [dictdiff(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs)]
        return b
    res = OrderedDict()
    if izipDestinationMatching in b:
        keepKey = b[izipDestinationMatching].attr
        del b[izipDestinationMatching]
    else:
        keepKey = izipDestinationMatching
    for key in sorted(set(a.keys() + b.keys())):
        v1 = a.get(key, None)
        v2 = b.get(key, None)
        if keepKey == key or v1 != v2: res[key] = dictdiff(v1, v2, searchAttrs)
    if len(res) <= 1: res = dict(res)  # This is only here for pretty print (OrderedDict doesn't pprint nicely)
    return res


def dictmerge(a, b, searchAttrs=[]):
    """
    Returns a dictionary which merges differences recorded in b to base dictionary a
    Also processes list values where the resulting list size will match that of a
    It can also search said list items (that are dicts) for identity values to detect changed positions
    @param a: original dict
    @param b: diff dict to patch into a
    @param searchAttrs: list of strings (keys to search for in sub-dicts)
    @return: dict / list / whatever input is
    """
    if not (isinstance(a, dict) and isinstance(b, dict)):
        if isinstance(a, list) and isinstance(b, list):
            return [dictmerge(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs, False)]
        return b
    res = OrderedDict()
    for key in sorted(set(a.keys() + b.keys())):
        v1 = a.get(key, None)
        v2 = b.get(key, None)
        #print "processing", key, v1, v2, key not in b, dictmerge(v1, v2)
        if v2 is not None: res[key] = dictmerge(v1, v2, searchAttrs)
        Elif key not in b: res[key] = v1
    if len(res) <= 1: res = dict(res)  # This is only here for pretty print (OrderedDict doesn't pprint nicely)
    return res
3
velis

動作する方法は次のとおりです。Falseに評価されるキーを許可し、可能な場合は早期に生成式を使用します。しかし、それは例外的にきれいではありません。

any(map(lambda x: True, (k for k in b if k not in a)))

編集:

THC4kは、別の回答に関する私のコメントへの返信を投稿しました。上記を行うより良い、よりきれいな方法は次のとおりです。

any(True for k in b if k not in a)

それが私の心を決して横切ったことはありません...

3
Steve Losh

Python≥2.7の場合:

# update different values in dictB
# I would assume only dictA should be updated,
# but the question specifies otherwise

for k in dictA.viewkeys() & dictB.viewkeys():
    if dictA[k] != dictB[k]:
        dictB[k]= dictA[k]

# add missing keys to dictA

dictA.update( (k,dictB[k]) for k in dictB.viewkeys() - dictA.viewkeys() )
2
tzot

スタンダートはどうですか(FULLオブジェクトと比較してください)

PyDev->新しいPyDevモジュール->モジュール:unittest

import unittest


class Test(unittest.TestCase):


    def testName(self):
        obj1 = {1:1, 2:2}
        obj2 = {1:1, 2:2}
        self.maxDiff = None # sometimes is usefull
        self.assertDictEqual(d1, d2)

if __== "__main__":
    #import sys;sys.argv = ['', 'Test.testName']

    unittest.main()
2
Maxx

2つの辞書の対称差の私のレシピ:

def find_dict_diffs(dict1, dict2):
    unequal_keys = []
    unequal_keys.extend(set(dict1.keys()).symmetric_difference(set(dict2.keys())))
    for k in dict1.keys():
        if dict1.get(k, 'N\A') != dict2.get(k, 'N\A'):
            unequal_keys.append(k)
    if unequal_keys:
        print 'param', 'dict1\t', 'dict2'
        for k in set(unequal_keys):
            print str(k)+'\t'+dict1.get(k, 'N\A')+'\t '+dict2.get(k, 'N\A')
    else:
        print 'Dicts are equal'

dict1 = {1:'a', 2:'b', 3:'c', 4:'d', 5:'e'}
dict2 = {1:'b', 2:'a', 3:'c', 4:'d', 6:'f'}

find_dict_diffs(dict1, dict2)

結果は次のとおりです。

param   dict1   dict2
1       a       b
2       b       a
5       e       N\A
6       N\A     f
1
smoke_lp

@Maxxには優れた答えがあります。Pythonが提供するunittestツールを使用してください。

import unittest


class Test(unittest.TestCase):
    def runTest(self):
        pass

    def testDict(self, d1, d2, maxDiff=None):
        self.maxDiff = maxDiff
        self.assertDictEqual(d1, d2)

次に、コード内のどこでも呼び出すことができます:

try:
    Test().testDict(dict1, dict2)
except Exception, e:
    print e

結果の出力はdiffからの出力のようになり、+または-で辞書をきれいに印刷し、異なる行を先頭に追加します。

1
Brent Washburne

3つ以上の辞書を比較できるソリューションを次に示します。

def diff_dict(dicts, default=None):
    diff_dict = {}
    # add 'list()' around 'd.keys()' for python 3 compatibility
    for k in set(sum([d.keys() for d in dicts], [])):
        # we can just use "values = [d.get(k, default) ..." below if 
        # we don't care that d1[k]=default and d2[k]=missing will
        # be treated as equal
        if any(k not in d for d in dicts):
            diff_dict[k] = [d.get(k, default) for d in dicts]
        else:
            values = [d[k] for d in dicts]
            if any(v != values[0] for v in values):
                diff_dict[k] = values
    return diff_dict

使用例:

import matplotlib.pyplot as plt
diff_dict([plt.rcParams, plt.rcParamsDefault, plt.matplotlib.rcParamsOrig])
1
tsvikas

他の回答で述べたように、unittestはdictを比較するためのNice出力を生成しますが、この例では、最初にテスト全体をビルドする必要はありません。

単体テストのソースをスクレイピングすると、これだけで公正なソリューションを得ることができるように見えます:

import difflib
import pprint

def diff_dicts(a, b):
    if a == b:
        return ''
    return '\n'.join(
        difflib.ndiff(pprint.pformat(a, width=30).splitlines(),
                      pprint.pformat(b, width=30).splitlines())
    )

そう

dictA = dict(Zip(range(7), map(ord, 'python')))
dictB = {0: 112, 1: 'spam', 2: [1,2,3], 3: 104, 4: 111}
print diff_dicts(dictA, dictB)

結果:

{0: 112,
-  1: 121,
-  2: 116,
+  1: 'spam',
+  2: [1, 2, 3],
   3: 104,
-  4: 111,
?        ^

+  4: 111}
?        ^

-  5: 110}

どこ:

  • 「-」は最初の辞書のキー/値を示しますが、2番目の辞書ではありません
  • 「+」は、最初の辞書ではなく、2番目の辞書のキー/値を示します

Unittestの場合と同様に、最後のマッピングは、末尾のカンマ/ブラケットのために、差分とみなすことができるという唯一の注意事項です。

1
Ryan de Kleer

2つの辞書キーを深く比較するためのソリューションを次に示します。

def compareDictKeys(dict1, dict2):
  if type(dict1) != dict or type(dict2) != dict:
      return False

  keys1, keys2 = dict1.keys(), dict2.keys()
  diff = set(keys1) - set(keys2) or set(keys2) - set(keys1)

  if not diff:
      for key in keys1:
          if (type(dict1[key]) == dict or type(dict2[key]) == dict) and not compareDictKeys(dict1[key], dict2[key]):
              diff = True
              break

  return not diff
1
Roei Bahumi

これを試して、両方の辞書にあるキーの交差を見つけます。2番目の辞書で見つからないキーが必要な場合は、not in ...を使用します。

intersect = filter(lambda x, dictB=dictB.keys(): x in dictB, dictA.keys())
0
Diogo Santiago

それがまだ関連しているかどうかはわかりませんが、この問題に遭遇しました、私の状況では、すべてのネストされた辞書などの変更の辞書を返す必要がありました。そこに良い解決策が見つかりませんでしたが、結局 this を実行する単純な関数を作成します。お役に立てれば、

0
Jonathan Mickle

Ghostdog74の回答に基づいて、

dicta = {"a":1,"d":2}
dictb = {"a":5,"d":2}

for value in dicta.values():
    if not value in dictb.values():
        print value

dictaの異なる値を出力します

0
normalUser

任意のdict構造と完全に比較するための組み込みソリューションが必要な場合は、@ Maxxの答えが良い出発点です。

import unittest

test = unittest.TestCase()
test.assertEqual(dictA, dictB)
0
Brad