Python: Bir sözlüğün başka bir büyük sözlüğün alt kümesi olup olmadığını kontrol edin


108

Keyfi sayıda kwarg alan ve bu kwargları içeren veritabanı benzeri bir listenin öğelerini içeren bir liste döndüren özel bir filtre yöntemi yazmaya çalışıyorum .

Örneğin, varsayalım d1 = {'a':'2', 'b':'3'}ve d2= aynı şeyi. d1 == d2True ile sonuçlanır. Ama varsayalım d2= aynı şey artı bir sürü başka şey. Metodumun d2'de d1 olup olmadığını söyleyebilmesi gerekiyor , ancak Python bunu sözlüklerle yapamaz.

Bağlam:

Bir kelime sınıfı vardır ve her bir nesne gibi özelliklere sahip olan word, definition, part_of_speechve böyle devam eder. Bu kelimelerin ana listesinde bir filtre yöntemi çağırabilmek istiyorum Word.objects.filter(word='jump', part_of_speech='verb-intransitive'). Bu anahtarları ve değerleri aynı anda nasıl yöneteceğimi çözemiyorum. Ancak bu, diğer insanlar için bu bağlamın dışında daha büyük işlevselliğe sahip olabilir.

Yanıtlar:


113

Öğe çiftlerine dönüştürün ve kapsamı kontrol edin.

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

Optimizasyon okuyucuya alıştırma olarak bırakılmıştır.


19
Dict değerleri) (viewitems kullanarak, hashable ise Aklıma gelen en optimizied yoludur: d1.viewitems() <= d2.viewitems(). Timeit çalıştırmaları 3 kattan fazla performans artışı gösterdi. Hashable değilse, iteritems()bunun yerine kullanmak bile items()yaklaşık 1.2x iyileştirme sağlar. Bu, Python 2.7 kullanılarak yapıldı.
Çad

36
Optimizasyonun okuyucuya bırakılması gerektiğini düşünmüyorum - insanların bunu, superset.items () 'in bir kopyasını oluşturacağını ve alt kümedeki her öğe için tekrarlayacağını fark etmeden kullanacağından endişeliyim.
robert king

5
Python 3 items()ile kopyalar yerine hafif görünümler döndürür. Daha fazla optimizasyona gerek yoktur.
Kentzo

3
İç içe dizinler ne olacak?
Andreas Profous

5
Bu komik. Mizah konusunu incelikli hale getirmeyi okuyucuya bırakacağım.
deepelement

111

Python 3'te, dict.items()dikte öğelerinin set benzeri bir görünümünü elde etmek için kullanabilirsiniz . Ardından, <=bir görünümün diğerinin "alt kümesi" olup olmadığını test etmek için operatörü kullanabilirsiniz :

d1.items() <= d2.items()

Python 2.7'de, dict.viewitems()aynısını yapmak için kullanın :

d1.viewitems() <= d2.viewitems()

Python 2.6 ve altında aşağıdakileri kullanmak gibi farklı bir çözüme ihtiyacınız olacak all():

all(key in d2 and d2[key] == d1[key] for key in d1)

1
python3 için bu şu olurd1.items() <= d2.items()
radu.ciorba

Uyarı: Programınız potansiyel olarak Python 2.6'da (veya hatta altında) kullanılabiliyorsa, d1.items() <= d2.items()aslında 2 listeyi belirli bir sıra olmadan karşılaştırıyorlar, bu nedenle nihai sonuç muhtemelen güvenilir olmayacaktır. Bu nedenle @blubberdiblub'un cevabına geçiyorum.
RayLuo

1
d1.items() <= d2.items()tanımsız bir davranıştır. Resmi belgelerde belgelenmemiştir ve en önemlisi test edilmemiştir: github.com/python/cpython/blob/… Yani bu uygulamaya bağlıdır.
Rodrigo Martins de Oliveira

2
@RodrigoMartins Bu belgelenir burada : "For sette benzeri görünümler, soyut temel sınıf için tanımlanmış işlemlerin hepsi collections.abc.Setmevcuttur"
augurar

1
@RodrigoMartins Gelecekteki bakımcılar için endişeleniyorsanız, işlemi açıkça adlandırılmış bir işlevle sarın veya bir kod yorumu ekleyin. Kod standartlarınızı yetersiz geliştiricilerin seviyesine indirmek korkunç bir fikirdir.
augurar

38

Birim testi için buna ihtiyaç duyan insanlar için not: assertDictContainsSubset()Python'un TestCasesınıfında da bir yöntem var .

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

Bununla birlikte, 3.2'de kullanımdan kaldırıldı, neden olduğundan emin değil, belki onun yerine geçebilir.


30
merak ediyordu, bunu 3.2'deki yeniliklerde buldum : assertDictContainsSubset () yöntemi, argümanlarla yanlış sırada yanlış uygulandığı için kullanımdan kaldırıldı. Bu, TestCase (). AssertDictContainsSubset ({'a': 1, 'b': 2}, {'a': 1}) gibi testlerin başarısız olacağı, hata ayıklaması zor optik illüzyonlar yarattı. (Katkıda bulunan Raymond Hettinger.)
Pedru

2
Bekle, sol taraf bekleniyor ve sağ taraf gerçek ... Bu başarısız olmaz mı? Fonksiyonla ilgili yanlış olan tek şey, hangisinin hangi yere gittiğinin kafa karıştırıcı olmasıdır?
JamesHutchison

21

anahtarlar ve değerler için kullanımı kontrol edin: set(d1.items()).issubset(set(d2.items()))

yalnızca anahtarları kontrol etmeniz gerekiyorsa: set(d1).issubset(set(d2))


11
Sözlüklerden herhangi birindeki herhangi bir değer karma hale getirilebilir değilse ilk ifade çalışmayacaktır.
Pedro Romano

6
İkinci örnek, (d2) kümesi kaldırılarak biraz kısaltılabilir, çünkü "issubset herhangi bir yinelenebilir" docs.python.org/2/library/stdtypes.html#set
trojjer

Bu yanlış: d1={'a':1,'b':2}; d2={'a':2,'b':1}-> ikinci pasaj geri dönecek True...
Francesco Pasa

1
@FrancescoPasa İkinci kod parçası açıkça şunu söylüyor: "Yalnızca anahtarları kontrol etmeniz gerekiyorsa". {'a', 'b'}aslında {'a', 'b'};)
DylanYoung

20

Tamlık için şunu da yapabilirsiniz:

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

Bununla birlikte, hız (veya eksikliği) veya okunabilirlik (veya bunların eksikliği) ile ilgili hiçbir iddiada bulunmuyorum.


Bir yan not: Bahsedilen diğer cevaplar small.viewitems() <= big.viewitems()umut vericiydi, ancak bir uyarı ile: programınız Python 2.6'da (veya hatta daha aşağıda) da kullanılabiliyorsa d1.items() <= d2.items(), gerçekte 2 listeyi belirli bir sıra olmadan karşılaştırıyor, bu nedenle nihai sonuç muhtemelen olacaktır. güvenilir değil. Bu nedenle @blubberdiblub'un cevabına geçiyorum. Olumlu oy verildi.
RayLuo

Bu harika, ancak iç içe geçmiş sözlüklerle çalışmıyor gibi görünüyor.
Frederik Baetens

@FrederikBaetens bunun anlamı yok. Ayrıca, bunu yapmanın genel olarak kabul edilmiş bir yolu olmadığına inanıyorum, çünkü bunun için gidebileceğiniz birçok yol var ve bu tür sözlüklere uygulayabileceğiniz birden fazla olası yapı / kısıtlama var. İşte akla gelen bazı sorular: Daha derin bir sözlüğe girilip girilmeyeceğini nasıl belirlersiniz? dictTemel sınıfa sahip türdeki nesneler ne olacak ? Ya yapmadıysa ve hala a gibi davranıyorsa dict? Ya bir eşleşen anahtarda hala dikte gibi davranan farklı türden değerler içeriyorsa smallve bigiçeriyorsa?
blubberdiblub

Bunlar geçerli noktalardır, ancak düz iç içe yerleştirilmiş diktlerle çalışan temel bir işlev iyi olmalıdır. Burada bir örnek yayınladım , ancak @ NutCracker'ın çözümü daha iyi
Frederik Baetens

Elbette, iç içe geçmiş sözlüklerle ilgili bir soru olsaydı (ve sözlükler için kesin gereklilikler belirtilmiş olsaydı), bunda bir çatlak olabilirdim. Buradaki mesele, iç içe geçmiş sözlükler için bir çözümün, bir diktenin bir başkasının alt yazısı olup olmadığını düz bir şekilde bilmek istediğinizde doğru cevabı vermemesidir (yani, cevabın kesinlikle Falsegeçen diktelerin değerleri olduğunda olmasını istediğinizde) anahtarları eşleştirmek için farklıdır). Veya başka bir deyişle: İç içe yerleştirilmiş diktler için çözüm, kullanım durumuna bağlı olarak zorunlu olarak bir ikame değildir.
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

bağlam:

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

5

İşte sözlükte yer alan listeler ve kümeler halinde de düzgün bir şekilde yinelenen bir çözüm. Bunu ayrıca dikteler vb. İçeren listeler için de kullanabilirsiniz.

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

4

İşlevim aynı amaç için, bunu yinelemeli olarak yapmak:

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

Örnekte, dictMatch(d1, d2)d2 içinde başka şeyler varsa Gerçek bile iade, ayrıca seviyelerini düşürmek için de geçerlidir olmalıdır:

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

dictMatch(d1, d2)   # True

Notlar: Maddeden kaçınan if type(pvalue) is dictve daha geniş bir yelpazedeki vakalar için geçerli olan daha iyi bir çözüm olabilir (karma listeleri vb.). Ayrıca yineleme burada sınırlı değildir, bu nedenle riski size aittir. ;)


2

Görünüşte basit olan bu sorun, araştırmada% 100 güvenilir bir çözüm bulmak için bana birkaç saat harcıyor, bu yüzden bu cevapta bulduğum şeyi belgeledim.

  1. "Pythonic-müttefiki" konuşma, small_dict <= big_dicten sezgisel yol olacaktır, ancak işe yaramayacak kadar kötü . {'a': 1} < {'a': 1, 'b': 2}Görünüşe göre Python 2'de çalışıyor, ancak güvenilir değil çünkü resmi belgeler açıkça belirtiyor. Go search "Eşitlik dışındaki sonuçlar tutarlı bir şekilde çözülür, ancak başka türlü tanımlanmaz." içinde bu bölümde . Python 3'te 2 dikti karşılaştırmak bir TypeError istisnası ile sonuçlanır.

  2. İkinci en sezgisel şey small.viewitems() <= big.viewitems()yalnızca Python 2.7 ve small.items() <= big.items()Python 3 içindir. Ancak bir uyarı var: potansiyel olarak hatalı . Programınız potansiyel olarak Python <= 2.6'da kullanılabiliyorsa, d1.items() <= d2.items()aslında 2 listeyi belirli bir sıra olmadan karşılaştırıyor demektir, bu nedenle nihai sonuç güvenilmez olur ve programınızda kötü bir hata olur. Python <= 2.6 için başka bir uygulama yazmak konusunda istekli değilim, ancak kodumun bilinen bir hata ile geldiği konusunda hala rahat hissetmiyorum (desteklenmeyen bir platformda olsa bile). Bu yüzden bu yaklaşımı terk ediyorum.

  3. Birlikte durulmak @blubberdiblub 'ın cevabı (Kredi ona gider):

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

    Bu cevabın ==, resmi belgede açıkça tanımlanmış olan dikteler arasındaki davranışa dayandığını ve bu nedenle her Python sürümünde çalışması gerektiğini belirtmekte fayda var . Git ara:

    • "Sözlükler, ancak ve ancak aynı (anahtar, değer) çiftlere sahiplerse eşit olarak karşılaştırılır." bu sayfadaki son cümledir
    • "Eşlemeler (dikte örnekleri) eşit (anahtar, değer) çiftleri varsa ve eşit olarak karşılaştırır. Anahtarların ve öğelerin eşitlik karşılaştırması, dönüşlülüğü zorlar." içinde bu sayfayı

2

Verilen sorun için genel bir özyinelemeli çözüm aşağıda verilmiştir:

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

NOT: Orijinal kod bazı durumlarda başarısız olabilir, düzeltme kredileri @ olivier-melançon'a gider


Kod doğrultusunda, bir liste içinde yuvalanmış dicti olan bir üst dizisi ile başarısızif not set(value) <= set(superset[key])
Eelco Hoogendoorn

2

Kullanmanın sakıncası pydash yoksa, is_matchtam olarak bunu yapan orada:

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

Bu sorunun eski olduğunu biliyorum, ancak iç içe geçmiş bir sözlüğün başka bir iç içe geçmiş sözlüğün parçası olup olmadığını kontrol etmek için benim çözümüm. Çözüm özyinelemelidir.

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

Bu işlev, hashable olmayan değerler için çalışır. Ayrıca net ve okunması kolay olduğunu düşünüyorum.

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

İç içe geçmiş sözlükler için çalışan kısa bir yinelemeli uygulama:

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

Bu a ve b dicts'i tüketecektir. Diğer cevaplarda olduğu gibi kısmen yinelemeli çözümlere başvurmadan bundan kaçınmanın iyi bir yolunu bilen biri varsa, lütfen bana söyleyin. Bir anahtara göre bir dikteyi baştan sona ayırmanın bir yolunu bulmalıyım.

Bu kod, bir programlama alıştırması olarak daha yararlıdır ve muhtemelen burada yineleme ve yinelemeyi karıştıran diğer çözümlerden çok daha yavaştır. @ Fındıkkıran'ın çözümü , iç içe geçmiş sözlükler için oldukça iyidir.


1
Kodda eksik olan bir şey var. Sadece buluntularda başlayan ilk değer a(ve sonraki herhangi bir ilk değer) aşağı iner popitem. Aynı seviyedeki diğer maddeleri de incelemelidir. Yanlış cevabı döndürdüğü iç içe geçmiş sözlerim var. (siparişine dayandığı için burada geleceğe dönük bir örnek sunmak zor popitem)
blubberdiblub

Teşekkürler, şimdi düzeltilmelidir :)
Frederik Baetens

0

Kısmi karşılaştırma ve hoş farklar sağlayan bu sarmalayıcı nesnesini kullanın:


class DictMatch(dict):
    """ Partial match of a dictionary to another one """
    def __eq__(self, other: dict):
        assert isinstance(other, dict)
        return all(other[name] == value for name, value in self.items())

actual_name = {'praenomen': 'Gaius', 'nomen': 'Julius', 'cognomen': 'Caesar'}
expected_name = DictMatch({'praenomen': 'Gaius'})  # partial match
assert expected_name == actual_name  # True
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.