Programing

Python : 한 사전이 다른 더 큰 사전의 하위 집합인지 확인

crosscheck 2020. 9. 11. 07:29
반응형

Python : 한 사전이 다른 더 큰 사전의 하위 집합인지 확인


임의의 수의 kwargs 를 사용하고 해당 kwargs 를 포함하는 데이터베이스와 같은 목록의 요소를 포함하는 목록을 반환 하는 사용자 지정 필터 메서드를 작성하려고합니다 .

예를 들어, d1 = {'a':'2', 'b':'3'}and d2= 같은 것을 가정 하십시오. d1 == d2True가됩니다. 그러나 d2= 같은 것 + 다른 것들이 있다고 가정 하십시오. 내 방법 은 d2의 d1 을 알 수 있어야 하지만 파이썬은 사전으로 그렇게 할 수 없습니다.

문맥:

Word 클래스가 있고 각 개체에는 word,, definition등의 속성이 있습니다 part_of_speech. 이 단어의 기본 목록에서 필터 메서드를 호출 할 수 있기를 원합니다 Word.objects.filter(word='jump', part_of_speech='verb-intransitive'). 이 키와 값을 동시에 관리하는 방법을 알 수 없습니다. 그러나 이것은 다른 사람들을 위해이 컨텍스트 밖에서 더 큰 기능을 가질 수 있습니다.


항목 쌍으로 변환하고 격리를 확인하십시오.

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)

단위 테스트를 위해 이것을 필요로하는 사람들을위한 참고 사항 : assertDictContainsSubset()Python의 TestCase클래스 에도 메서드가 있습니다.

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

그러나 3.2에서는 더 이상 사용되지 않으며 이유가 확실하지 않으며 대체가있을 수 있습니다.


키 및 값 확인 사용 : set(d1.items()).issubset(set(d2.items()))

키만 확인해야하는 경우 : set(d1).issubset(set(d2))


완전성을 위해 다음을 수행 할 수도 있습니다.

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

그러나 나는 속도 (또는 속도 부족) 또는 가독성 (또는 부족)과 관련하여 어떠한 주장도하지 않습니다.


>>> 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
>>>

같은 목적으로 내 기능을 재귀 적으로 수행합니다.

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을 피하고 더 넓은 범위의 경우 (해시 목록 등)에 적용 하는 더 나은 솔루션이있을 수 있습니다 . 또한 재귀는 여기에 제한되지 않으므로 자신의 책임하에 사용하십시오. ;)


이 겉보기에 간단 해 보이는 문제는 100 % 신뢰할 수있는 솔루션을 찾기 위해 연구하는 데 몇 시간이 걸리므로이 답변에서 찾은 내용을 문서화했습니다.

  1. "Pythonic-ally" small_dict <= big_dict는 가장 직관적 인 방법이지만 작동하지 않을 정도로 나쁘다 . {'a': 1} < {'a': 1, 'b': 2}겉보기에는 Python 2에서 작동하지만 공식 문서에서 명시 적으로 언급하기 때문에 신뢰할 수 없습니다. Go search "평등 이외의 결과는 일관되게 해결되지만 달리 정의되지 않았습니다." 에서 이 섹션 . 말할 것도없이 Python 3에서 2 개의 사전을 비교하면 TypeError 예외가 발생합니다.

  2. The second most-intuitive thing is small.viewitems() <= big.viewitems() for Python 2.7 only, and small.items() <= big.items() for Python 3. But there is one caveat: it is potentially buggy. If your program could potentially be used on Python <=2.6, its d1.items() <= d2.items() are actually comparing 2 lists of tuples, without particular order, so the final result will be unreliable and it becomes a nasty bug in your program. I am not keen to write yet another implementation for Python<=2.6, but I still don't feel comfortable that my code comes with a known bug (even if it is on an unsupported platform). So I abandon this approach.

  3. I settle down with @blubberdiblub 's answer (Credit goes to him):

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

    이 답변은 ==공식 문서에 명확하게 정의되어 있으므로 모든 Python 버전에서 작동해야하는 dict 간의 동작에 의존한다는 점을 지적 할 가치가 있습니다. 검색하기 :

    • "사전은 동일한 (키, 값) 쌍이있는 경우에만 동일하게 비교합니다." 이 페이지 의 마지막 문장입니다 .
    • "매핑 (dict의 인스턴스)은 동일한 (키, 값) 쌍이있는 경우에만 동일하게 비교합니다. 키와 요소의 동등 비교는 반사성을 강제합니다." 에서 이 페이지

주어진 문제에 대한 일반적인 재귀 솔루션은 다음과 같습니다.

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 __name__ == "__main__":
    unittest.main()

참고 : 원래 코드는 어떤 경우에는 실패의 학점 정부는 에 간다 @ 올리비에 - melançon


I know this question is old, but here is my solution for checking if one nested dictionary is a part of another nested dictionary. The solution is recursive.

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

This function works for non-hashable values. I also think that it is clear and easy to read.

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

If you don't mind using pydash there is is_match there which does exactly that:

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







A short recursive implementation that works for nested dictionaries:

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

This will consume the a and b dicts. If anyone knows of a good way to avoid that without resorting to partially iterative solutions as in other answers, please tell me. I would need a way to split a dict into head and tail based on a key.

This code is more usefull as a programming exercise, and probably is a lot slower than other solutions in here that mix recursion and iteration. @Nutcracker's solution is pretty good for nested dictionaries.


Here is a solution that also properly recurses into lists and sets contained within the dictionary. You can also use this for lists containing dicts etc...

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

참고URL : https://stackoverflow.com/questions/9323749/python-check-if-one-dictionary-is-a-subset-of-another-larger-dictionary

반응형