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


100

임의의 수의 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'). 이 키와 값을 동시에 관리하는 방법을 알 수 없습니다. 그러나 이것은 다른 사람들을 위해이 컨텍스트 밖에서 더 큰 기능을 가질 수 있습니다.

답변:


108

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

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

최적화는 독자를위한 연습으로 남겨집니다.


17
dict 값이 해시 가능한 경우 viewitems ()를 사용하는 것이 제가 생각할 수있는 가장 최적화 된 방법 d1.viewitems() <= d2.viewitems()입니다. Timeit 실행은 3 배 이상의 성능 향상을 보여주었습니다. 해시 할 수없는 경우 iteritems()대신 사용하더라도 items()약 1.2 배 향상됩니다. 이것은 Python 2.7을 사용하여 수행되었습니다.
Chad

34
저는 최적화가 독자에게 맡겨 져야한다고 생각하지 않습니다. 사람들이 superset.items ()의 복사본을 만들고 하위 집합의 모든 항목에 대해 반복 할 것이라는 사실을 깨닫지 못하고 실제로 이것을 사용할 것이라고 걱정합니다.
로버트 왕

4
Python 3 items()에서는 복사본 대신 경량 뷰를 반환합니다. 추가 최적화가 필요하지 않습니다.
Kentzo

3
중첩 된 디렉토리는 어떻습니까?
Andreas Profous 2010 년

5
이것은 재밌습니다. 유머의 주제 수정은 독자에게 맡기겠습니다.
deepelement

95

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)

1
python3의 경우 이것은d1.items() <= d2.items()
radu.ciorba

주의 사항 : 프로그램이 Python 2.6 (또는 그 이하)에서 잠재적으로 사용될 수 있다면는 d1.items() <= d2.items()실제로 2 개의 튜플 목록을 특별한 순서없이 비교하므로 최종 결과는 신뢰할 수 없을 것입니다. 이런 이유로 @blubberdiblub의 답변으로 전환합니다.
RayLuo

1
d1.items() <= d2.items()정의되지 않은 동작입니다. 공식 문서에 문서화되어 있지 않으며 가장 중요한 것은 테스트되지 않았다는 것입니다 : github.com/python/cpython/blob/… 따라서 이것은 구현에 따라 다릅니다.
Rodrigo Martins de Oliveira

2
@RodrigoMartins 여기에 문서화되어 있습니다 . "세트와 유사한 뷰의 경우 추상 기본 클래스에 대해 정의 된 모든 작업 collections.abc.Set을 사용할 수 있습니다."
augurar

1
@RodrigoMartins 미래의 관리자가 걱정된다면 명확한 이름의 함수로 작업을 감싸거나 코드 주석을 추가하십시오. 코드 표준을 무능한 개발자 수준으로 낮추는 것은 끔찍한 생각입니다.
augurar

36

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

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

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


29
호기심이 발견 된 3.2의 새로운 기능 : 이 잘못된 순서로 인수 misimplemented했기 때문에 assertDictContainsSubset () 메서드가 사용됩니다. 이로 인해 TestCase (). assertDictContainsSubset ({ 'a': 1, 'b': 2}, { 'a': 1})과 같은 테스트가 실패하는 디버그하기 어려운 착시 현상이 발생했습니다. (기고 : Raymond Hettinger.)
Pedru

2
잠깐, 왼쪽은 예상하고 오른쪽은 실제 ... 실패하지 않나요? 함수의 유일한 잘못된 점은 어느 쪽이 어느 곳으로 가는지 혼란 스럽다는 것입니다.
JamesHutchison

21

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

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


11
사전 중 하나의 값이 해시 할 수없는 경우 첫 번째 표현식이 작동하지 않습니다.
Pedro Romano

6
두 번째 예제는 "issubset이 모든 반복 가능 항목을 허용하므로"set (d2)를 제거하여 약간 줄일 수 있습니다. docs.python.org/2/library/stdtypes.html#set
trojjer

이것은 잘못된 것입니다 : d1={'a':1,'b':2}; d2={'a':2,'b':1}-> 두 번째 조각이 돌아갑니다 True...
프란체스코 파사

1
@FrancescoPasa 두 번째 스 니펫은 "키만 확인해야하는 경우"라고 명시 적으로 말합니다. {'a', 'b'}의 부분 집합은 사실이다 {'a', 'b'})
DylanYoung

19

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

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

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


참고 사항 : 언급 된 다른 답변 small.viewitems() <= big.viewitems()은 유망했지만 한 가지주의 사항이 있습니다. 프로그램이 Python 2.6 (또는 그 이하)에서도 사용될 수 있다면 d1.items() <= d2.items()실제로 두 개의 튜플 목록을 특별한 순서없이 비교하므로 최종 결과는 아마도 신뢰할 수 없습니다. 그런 이유로 @blubberdiblub의 답변으로 전환합니다. 찬성.
RayLuo

이것은 멋지지만 중첩 된 사전에서는 작동하지 않는 것 같습니다.
Frederik Baetens 2019 년

@FrederikBaetens는 의미가 없습니다. 또한 일반적으로 허용되는 방법은 없다고 생각합니다. 왜냐하면 여러 가지 방법이 있고 그러한 사전에 부과 할 수있는 여러 가능한 구조 / 제한 사항이 있기 때문입니다. 떠오르는 몇 가지 질문은 다음과 같습니다. 더 깊은 사전을 내려야하는지 여부를 어떻게 결정합니까? dict기본 클래스로 있는 유형의 객체는 어떻습니까? 그렇지 않고 여전히 작동한다면 dict? 어떤 경우 smallbig여전히 딕셔너리처럼 행동 일치하는 키에 다른 유형의 값을 포함?
blubberdiblub

그것들은 유효한 포인트이지만 일반 중첩 dicts와 함께 작동하는 기본 함수가 좋습니다. 나는 예를 게시 여기 지만, @ 호두 까기 인형의 솔루션은 더 나은
프레데릭 Baetens

물론, 중첩 된 사전에 대한 질문이었고 사전에 대한 정확한 요구 사항이 설명되어 있었다면 그에 대한 균열이 있었을 것입니다. 요점은 중첩 된 사전에 대한 솔루션이 딕셔너리가 다른 딕셔너리의 서브 딕트인지 알고 싶을 때 (즉 False, 전달 된 딕셔너리의 값이 일치하는 키에 따라 다릅니다). 즉, 중첩 된 사전에 대한 솔루션이 사용 사례에 따라 반드시 드롭 인 대체가되는 것은 아닙니다.
blubberdiblub

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

4

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

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

다음은 사전에 포함 된 목록과 집합으로 적절하게 반복되는 솔루션입니다. 딕셔너리 등을 포함하는 목록에도 이것을 사용할 수 있습니다.

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

2

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

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

  2. 두 번째로 가장 직관적 인 것은 small.viewitems() <= big.viewitems()Python 2.7과 small.items() <= big.items()Python 3 전용입니다.하지만 한 가지주의 할 점이 있습니다 . 잠재적으로 버그 가있을 있습니다. 프로그램이 Python <= 2.6에서 잠재적으로 사용될 수 있다면 d1.items() <= d2.items()실제로는 특정 순서없이 두 개의 튜플 목록을 비교하는 것이므로 최종 결과는 신뢰할 수 없으며 프로그램에서 심각한 버그가됩니다. Python <= 2.6에 대한 또 다른 구현을 작성하고 싶지는 않지만 여전히 내 코드가 알려진 버그와 함께 제공된다는 사실이 마음에 들지 않습니다 (지원되지 않는 플랫폼에 있더라도). 그래서 저는이 접근 방식을 포기합니다.

  3. 나는 @blubberdiblub의 답변으로 정착했습니다 (크레딧이 그에게갑니다).

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

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

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

2

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

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


코드 라인에서, 목록 안에 중첩 된 딕셔너리가있는 상위 실패if not set(value) <= set(superset[key])
Eelco Hoogendoorn에게

2

사용 괜찮다면 pydashis_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

1

이 질문이 오래되었다는 것을 알고 있지만 중첩 사전이 다른 중첩 사전의 일부인지 확인하는 솔루션이 있습니다. 솔루션은 재귀 적입니다.

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

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

중첩 된 사전에 대해 작동하는 짧은 재귀 구현 :

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 dicts를 소비합니다. 다른 답변에서와 같이 부분적으로 반복되는 솔루션에 의존하지 않고 그것을 피할 수있는 좋은 방법을 아는 사람이 있다면 알려주십시오. 키를 기반으로 딕셔너리를 헤드와 테일로 분할하는 방법이 필요합니다.

이 코드는 프로그래밍 연습으로 더 유용하며 재귀와 반복을 혼합하는 다른 솔루션보다 훨씬 느립니다. @Nutcracker의 솔루션 은 중첩 사전에 매우 좋습니다.


1
코드에 빠진 것이 있습니다. 에서 시작하는 첫 번째 값 a(및 이후의 모든 첫 번째 값)이 popitem찾습니다. 같은 수준의 다른 항목도 검사해야합니다. 잘못된 답변을 반환하는 중첩 된 dicts 쌍이 있습니다. (순서에 의존하기 때문에 여기에 미래 보장형 예를 제시하기 어렵습니다. popitem)
blubberdiblub

감사합니다 :) 이제 수정되어야합니다
프레데릭 Baetens에게
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.