web-dev-qa-db-ja.com

Python:1つの辞書が別のより大きな辞書のサブセットであるかどうかを確認します

私は任意の数のkwargsを取り、以下を含むデータベースのようなリストの要素を含むリストを返すカスタムフィルターメソッドを記述しようとしています。それらkwargs

たとえば、_d1 = {'a':'2', 'b':'3'}_と_d2_ =同じことを想定します。 _d1 == d2_はTrueになります。しかし、_d2_ =同じものに加えて他の多くのものを考えてください。私のメソッドは、d1 in d2かどうかを判断できる必要がありますが、Pythonは辞書ではできません。

コンテキスト:

Wordクラスがあり、各オブジェクトにはWorddefinition、_part_of_speech_などのプロパティがあります。 Word.objects.filter(Word='jump', part_of_speech='verb-intransitive')のように、これらの単語のメインリストでフィルターメソッドを呼び出すことができるようにします。これらのキーと値を同時に管理する方法がわかりません。しかし、これは他の人にとってはこの文脈の外でより大きな機能を持つことができます。

83
Jamey

アイテムのペアに変換し、封じ込めを確認します。

all(item in superset.items() for item in subset.items())

最適化は読者の演習として残されています。

Python 3)では、dict.items()を使用してdictアイテムのセットのようなビューを取得できます。その後、_<=_演算子を使用して、ビューはもう一方の「サブセット」です。

_d1.items() <= d2.items()
_

Python 2.7では、dict.viewitems()を使用して同じことを行います。

_d1.viewitems() <= d2.viewitems()
_

Python 2.6以下では、all()を使用するなど、別のソリューションが必要になります。

_all(key in d2 and d2[key] == d1[key] for key in d1)
_
68
augurar

単体テストでこれを必要とする人々への注意:PythonのTestCaseクラスにはassertDictContainsSubset()メソッドもあります。

http://docs.python.org/2/library/unittest.html?highlight=assertdictcontainssubset#unittest.TestCase.assertDictContainsSubset

ただし、3.2では非推奨になりましたが、その理由は定かではありませんが、代わりになる可能性があります。

32
gitaarik

キーと値のチェックに使用:set(d1.items()).issubset(set(d2.items()))

キーのみをチェックする必要がある場合:set(d1).issubset(set(d2))

20
kashchey

完全を期すために、次のこともできます。

def is_subdict(small, big):
    return dict(big, **small) == big

ただし、速度(またはその欠如)または読みやすさ(またはその欠如)については一切主張しません。

17
blubberdiblub
>>> d1 = {'a':'2', 'b':'3'}
>>> d2 = {'a':'2', 'b':'3','c':'4'}
>>> all((k in d2 and d2[k]==v) for k,v in d1.iteritems())
True

コンテキスト:

>>> d1 = {'a':'2', 'b':'3'}
>>> d2 = {'a':'2', 'b':'3','c':'4'}
>>> list(d1.iteritems())
[('a', '2'), ('b', '3')]
>>> [(k,v) for k,v in d1.iteritems()]
[('a', '2'), ('b', '3')]
>>> k,v = ('a','2')
>>> k
'a'
>>> v
'2'
>>> k in d2
True
>>> d2[k]
'2'
>>> k in d2 and d2[k]==v
True
>>> [(k in d2 and d2[k]==v) for k,v in d1.iteritems()]
[True, True]
>>> ((k in d2 and d2[k]==v) for k,v in d1.iteritems())
<generator object <genexpr> at 0x02A9D2B0>
>>> ((k in d2 and d2[k]==v) for k,v in d1.iteritems()).next()
True
>>> all((k in d2 and d2[k]==v) for k,v in d1.iteritems())
True
>>>
10
robert king

これを再帰的に行う同じ目的の私の関数:

_def dictMatch(patn, real):
    """does real dict match pattern?"""
    try:
        for pkey, pvalue in patn.iteritems():
            if type(pvalue) is dict:
                result = dictMatch(pvalue, real[pkey])
                assert result
            else:
                assert real[pkey] == pvalue
                result = True
    except (AssertionError, KeyError):
        result = False
    return result
_

あなたの例では、dictMatch(d1, d2)は、d2に他のものが含まれている場合でもTrueを返す必要があります。さらに、下位レベルにも適用されます。

_d1 = {'a':'2', 'b':{3: 'iii'}}
d2 = {'a':'2', 'b':{3: 'iii', 4: 'iv'},'c':'4'}

dictMatch(d1, d2)   # True
_

注:if type(pvalue) is dict句を使用せず、より広い範囲のケース(ハッシュのリストなど)に適用する、さらに優れたソリューションがあります。また、再帰はここでは制限されないため、自己責任で使用してください。 ;)

4
Alois Mahdal

私はこの質問が古いことを知っていますが、ネストされた辞書が別のネストされた辞書の一部であるかどうかをチェックするための私のソリューションです。ソリューションは再帰的です。

def compare_dicts(a, b):
    for key, value in a.items():
        if key in b:
            if isinstance(a[key], dict):
                if not compare_dicts(a[key], b[key]):
                    return False
            Elif value != b[key]:
                return False
        else:
            return False
    return True
2
NutCracker

与えられた問題に対する一般的な再帰的解法は次のとおりです。

import traceback
import unittest

def is_subset(superset, subset):
    for key, value in subset.items():
        if key not in superset:
            return False

        if isinstance(value, dict):
            if not is_subset(superset[key], value):
                return False

        Elif isinstance(value, str):
            if value not in superset[key]:
                return False

        Elif isinstance(value, list):
            if not set(value) <= set(superset[key]):
                return False
        Elif isinstance(value, set):
            if not value <= superset[key]:
                return False

        else:
            if not value == superset[key]:
                return False

    return True


class Foo(unittest.TestCase):

    def setUp(self):
        self.dct = {
            'a': 'hello world',
            'b': 12345,
            'c': 1.2345,
            'd': [1, 2, 3, 4, 5],
            'e': {1, 2, 3, 4, 5},
            'f': {
                'a': 'hello world',
                'b': 12345,
                'c': 1.2345,
                'd': [1, 2, 3, 4, 5],
                'e': {1, 2, 3, 4, 5},
                'g': False,
                'h': None
            },
            'g': False,
            'h': None,
            'question': 'mcve',
            'metadata': {}
        }

    def tearDown(self):
        pass

    def check_true(self, superset, subset):
        return self.assertEqual(is_subset(superset, subset), True)

    def check_false(self, superset, subset):
        return self.assertEqual(is_subset(superset, subset), False)

    def test_simple_cases(self):
        self.check_true(self.dct, {'a': 'hello world'})
        self.check_true(self.dct, {'b': 12345})
        self.check_true(self.dct, {'c': 1.2345})
        self.check_true(self.dct, {'d': [1, 2, 3, 4, 5]})
        self.check_true(self.dct, {'e': {1, 2, 3, 4, 5}})
        self.check_true(self.dct, {'f': {
            'a': 'hello world',
            'b': 12345,
            'c': 1.2345,
            'd': [1, 2, 3, 4, 5],
            'e': {1, 2, 3, 4, 5},
        }})
        self.check_true(self.dct, {'g': False})
        self.check_true(self.dct, {'h': None})

    def test_tricky_cases(self):
        self.check_true(self.dct, {'a': 'hello'})
        self.check_true(self.dct, {'d': [1, 2, 3]})
        self.check_true(self.dct, {'e': {3, 4}})
        self.check_true(self.dct, {'f': {
            'a': 'hello world',
            'h': None
        }})
        self.check_false(
            self.dct, {'question': 'mcve', 'metadata': {'author': 'BPL'}})
        self.check_true(
            self.dct, {'question': 'mcve', 'metadata': {}})
        self.check_false(
            self.dct, {'question1': 'mcve', 'metadata': {}})

if __== "__main__":
    unittest.main()

注:特定の場合、元のコードは失敗します。 fixing のクレジットは @olivier-melançon になります

2
BPL

この一見単純な問題により、100%信頼性の高いソリューションを見つけるための調査に数時間かかるため、この回答で見つけたものを文書化しました。

  1. "Pythonic-ally"と言えば、_small_dict <= big_dict_が最も直感的な方法ですが、あまりにもひどいため動作しません。 _{'a': 1} < {'a': 1, 'b': 2}_はPython 2で動作しているように見えますが、公式のドキュメントでは明示的に呼び出されているため、信頼できません。 「平等以外の結果は一貫して解決されますが、他の方法で定義されていません」検索を実行します。 in このセクション 。言うまでもなく、Python 3の2つの辞書を比較すると、TypeError例外が発生します。

  2. 2番目に直感的なのは、Python 2.7のみのsmall.viewitems() <= big.viewitems()と、Python 3.のsmall.items() <= big.items()です。しかし、注意が1つあります。 潜在的にバギーです。プログラムがPython <= 2.6で使用される可能性がある場合、そのd1.items() <= d2.items()は実際には特定の順序なしに2つのタプルのリストを比較しているため、最終結果は信頼できず、プログラムの厄介なバグ。 Python <= 2.6の別の実装を作成するつもりはありませんが、(サポートされていないプラットフォーム上であっても)コードに既知のバグが含まれていることに不安を感じています。だから私はこのアプローチを放棄します。

  3. @ blubberdiblubの答え (クレジットは彼に行きます)で落ち着きます:

    def is_subdict(small, big): return dict(big, **small) == big

    この答えは、辞書間の_==_動作に依存していることを指摘する価値があります。これは公式文書で明確に定義されているため、はすべてのPythonバージョン。検索に行く:

    • 「辞書は、同じ(キー、値)ペアを持っている場合にのみ等しいと比較します。」 このページ の最後の文です
    • 「マッピング(dictのインスタンス)は、等しい(キー、値)ペアがある場合にのみ等しいと比較します。キーと要素の等値比較は、再帰性を強制します。」 in このページ
2
RayLuo

ネストされた辞書で機能する短い再帰実装:

def compare_dicts(a,b):
    if not a: return True
    if isinstance(a, dict):
        key, val = a.popitem()
        return isinstance(b, dict) and key in b and compare_dicts(val, b.pop(key)) and compare_dicts(a, b)
    return a == b

これにより、aおよびbのディクテーションが消費されます。他の回答のように部分的に反復的な解決策に頼らずにそれを回避する良い方法を誰かが知っているなら、教えてください。キーに基づいて、辞書を頭と尾に分割する方法が必要です。

このコードは、プログラミング演習としてより有用であり、おそらく、再帰と反復を混在させる他のソリューションよりもはるかに低速です。 @ Nutcracker's solution は、ネストされた辞書に適しています。

0

Pydashを使用してもかまわない場合は、is_matchがあります。

import pydash

a = {1:2, 3:4, 5:{6:7}}
b = {3:4.0, 5:{6:8}}
c = {3:4.0, 5:{6:7}}

pydash.predicates.is_match(a, b) # False
pydash.predicates.is_match(a, c) # True






0
Zaro

以下は、辞書に含まれるリストとセットにも適切に再帰するソリューションです。また、辞書などを含むリストにもこれを使用できます...

def is_subset(subset, superset):
    if isinstance(subset, dict):
        return all(key in superset and is_subset(val, superset[key]) for key, val in subset.items())

    if isinstance(subset, list) or isinstance(subset, set):
        return all(any(is_subset(subitem, superitem) for superitem in superset) for subitem in subset)

    # assume that subset is a plain value if none of the above match
    return subset == superset
0

この関数は、ハッシュできない値に対して機能します。また、明確で読みやすいと思います。

def isSubDict(subDict,dictionary):
    for key in subDict.keys():
        if (not key in dictionary) or (not subDict[key] == dictionary[key]):
            return False
    return True

In [126]: isSubDict({1:2},{3:4})
Out[126]: False

In [127]: isSubDict({1:2},{1:2,3:4})
Out[127]: True

In [128]: isSubDict({1:{2:3}},{1:{2:3},3:4})
Out[128]: True

In [129]: isSubDict({1:{2:3}},{1:{2:4},3:4})
Out[129]: False
0
timthelion