Tüm iç içe geçmiş sözlük değerleri arasında döngü yapılsın mı?


121
for k, v in d.iteritems():
    if type(v) is dict:
        for t, c in v.iteritems():
            print "{0} : {1}".format(t, c)

Bir sözlükte dolaşmaya ve değerin iç içe geçmiş bir sözlük olmadığı tüm anahtar değer çiftlerini yazdırmaya çalışıyorum. Değer bir sözlükse, ona girmek ve anahtar değer çiftlerini yazdırmak istiyorum ... vb. Herhangi bir yardım?

DÜZENLE

Buna ne dersin? Hala yalnızca bir şey yazdırıyor.

def printDict(d):
    for k, v in d.iteritems():
        if type(v) is dict:
            printDict(v)
        else:
            print "{0} : {1}".format(k, v)

Tam Test Örneği

Sözlük:

{u'xml': {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'},
      u'port': u'11'}}

Sonuç:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}

1
Yineleme istiyormuşsunuz gibi görünüyor, ancak açıklama emin olmak için yeterince açık değil. Peki ya bazı örnek giriş / çıkış? Ayrıca, kodunuzun nesi var?
Niklas B.

2
Python'da sabit bir özyineleme sınırı vardır: docs.python.org/library/sys.html#sys.setrecursionlimit
-Philip Gehrcke

2
@ Jan-PhilipGehrcke: Algoritmaları ağaca benzer bir veri yapısında özyineleme olmadan uygulamak tamamen intihar demektir.
Niklas B.

2
@Takkun: dictDeğişken adı kullanıyorsunuz. Bunu asla yapmayın (bu yüzden başarısız olur).
Niklas B.

3
@NiklasB., Re: "intihar": Scharron algoritmasının yinelemeli bir versiyonunu uyguladım ve sadece iki satırı daha uzun ve takip etmesi oldukça kolay. Ayrıca, ağaçlardan genel grafiklere geçerken özyinelemeyi yinelemeye çevirmek genellikle bir gerekliliktir.
Fred Foo

Yanıtlar:


158

Niklas'ın söylediği gibi, özyinelemeye ihtiyacınız var, yani diktenizi yazdırmak için bir işlev tanımlamak istiyorsunuz ve değer bir dikteyse, bu yeni dikteyi kullanarak yazdırma işlevinizi çağırmak istiyorsunuz.

Gibi bir şey :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))

3
küçük gelişme. myprint (v) 'i çağırmadan önce print (k) ekleyin.
Naomi Fridman

Özyineleme ile önemsizdir.
sergzach

37

Kendi özyinelemeli uygulamanızı veya stack ile yinelemeli eşdeğerini yazarsanız olası sorunlar vardır . Bu örneğe bakın:

    dic = {}
    dic["key1"] = {}
    dic["key1"]["key1.1"] = "value1"
    dic["key2"]  = {}
    dic["key2"]["key2.1"] = "value2"
    dic["key2"]["key2.2"] = dic["key1"]
    dic["key2"]["key2.3"] = dic

Normal anlamda, iç içe geçmiş sözlük, hiç olmayan ağaç benzeri veri yapısı olacaktır. Ancak tanım , bir çapraz kenar veya hatta bir arka kenar olasılığını dışlamaz (bu nedenle artık bir ağaç değildir). Örneğin, burada key2.2 sözlüğe tutan anahtar1 , key2.3 sözlükteki bütün noktaları (arka kenar / çevrim). Bir arka kenar (döngü) olduğunda, yığın / özyineleme sonsuza kadar çalışacaktır.

                          root<-------back edge
                        /      \           |
                     _key1   __key2__      |
                    /       /   \    \     |
               |->key1.1 key2.1 key2.2 key2.3
               |   /       |      |
               | value1  value2   |
               |                  | 
              cross edge----------|

Bu sözlüğü Scharron'un bu uygulamasıyla yazdırırsanız

    def myprint(d):
      for k, v in d.items():
        if isinstance(v, dict):
          myprint(v)
        else:
          print "{0} : {1}".format(k, v)

Bu hatayı görürsünüz:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Aynı şey senderle'dan uygulama için de geçerli .

Benzer şekilde, Fred Foo'dan bu uygulama ile sonsuz bir döngü elde edersiniz :

    def myprint(d):
        stack = list(d.items())
        while stack:
            k, v = stack.pop()
            if isinstance(v, dict):
                stack.extend(v.items())
            else:
                print("%s: %s" % (k, v))

Bununla birlikte, Python aslında iç içe geçmiş sözlükteki döngüleri algılar:

    print dic
    {'key2': {'key2.1': 'value2', 'key2.3': {...}, 
       'key2.2': {'key1.1': 'value1'}}, 'key1': {'key1.1': 'value1'}}

"{...}" , bir döngünün algılandığı yerdir.

Moondra tarafından talep edildiği gibi bu, döngüleri (DFS) önlemenin bir yoludur:

def myprint(d): 
  stack = list(d.items()) 
  visited = set() 
  while stack: 
    k, v = stack.pop() 
    if isinstance(v, dict): 
      if k not in visited: 
        stack.extend(v.items()) 
      else: 
        print("%s: %s" % (k, v)) 
      visited.add(k)

Öyleyse, yinelemeli bir çözümü nasıl uygularsınız?
dreftymac

2
@dreftymac Döngüden kaçınmak için ziyaret edilen bir anahtar seti def myprint(d): stack = d.items() visited = set() while stack: k, v = stack.pop() if isinstance(v, dict): if k not in visited: stack.extend(v.iteritems()) else: print("%s: %s" % (k, v)) visited.add(k)
eklerdim

1
Bunu işaret ettiğiniz için teşekkürler. Kodunuzu cevaba dahil eder misiniz? Mükemmel cevabınızı tamamladığını düşünüyorum.
Moondra

Python3 için, liste olarak değil bir görünüm döndürür list(d.items())olarak kullanın d.items()ve v.items()bunun yerine kullanınv.iteritems()
Fazla

33

A dictyinelenebilir olduğundan, klasik iç içe geçmiş kapsayıcı yinelenebilir formülünü bu soruna yalnızca birkaç küçük değişiklikle uygulayabilirsiniz . İşte bir Python 2 sürümü (3 için aşağıya bakın):

import collections
def nested_dict_iter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in nested_dict_iter(value):
                yield inner_key, inner_value
        else:
            yield key, value

Ölçek:

list(nested_dict_iter({'a':{'b':{'c':1, 'd':2}, 
                            'e':{'f':3, 'g':4}}, 
                       'h':{'i':5, 'j':6}}))
# output: [('g', 4), ('f', 3), ('c', 1), ('d', 2), ('i', 5), ('j', 6)]

Python 2'de, olarak nitelenen ancak içermeyen bir özel oluşturmak mümkün olabilir , bu durumda bu başarısız olur. Dokümanlar , a için gerekli olduğunu göstermez ; Öte yandan, kaynak verir tipleri bir yöntem. Yani özel için , her ihtimale karşı açık bir şekilde devralın .MappingMappingiteritemsiteritemsMappingMappingiteritemsMappingscollections.Mapping

Python 3'te yapılacak bir dizi iyileştirme var. Python 3.3'ten itibaren, soyut temel sınıflar collections.abc. collectionsGeriye dönük uyumluluk için çok kalırlar , ancak soyut temel sınıflarımızı tek bir ad alanında bir araya getirmek daha güzel. Bu ithalat Yani abcgelen collections. Python 3.3 yield from, sadece bu tür durumlar için tasarlanmış olanı da ekler . Bu boş sözdizimsel şeker değildir; daha hızlı koda ve eşgüdümlerle daha mantıklı etkileşimlere yol açabilir .

from collections import abc
def nested_dict_iter(nested):
    for key, value in nested.items():
        if isinstance(value, abc.Mapping):
            yield from nested_dict_iter(value)
        else:
            yield key, value

3
isinstance(item, collections.Iterable)garantisi yoktur hasattr(item, "iteritems"). Kontrol etmek collections.Mappingdaha iyidir.
Fred Foo

1
@larsmans, oldukça haklısın elbette. Kullanmanın Iterablebu çözümü daha genel hale getireceğini düşünüyordum , açıkçası, yinelemelerin mutlaka sahip olmadığını unutuyordum iteritems.
56'da gönderen

Bu yanıta +1, çünkü bu sorun için çalışan genel bir çözümdür, ancak yalnızca değerleri yazdırmakla sınırlı değildir. @Takkun bu seçeneği kesinlikle düşünmelisiniz. Uzun vadede, değerleri yazdırmaktan daha fazlasını isteyeceksiniz.
Alejandro piad

1
@ Seanny123, Buna dikkatimi çektiğiniz için teşekkürler. Python 3 resmi birkaç şekilde değiştiriyor, aslında - bunu yeni yield fromsözdizimini kullanan bir sürüm olarak yeniden yazacağım .
56'da gönderen

25

Alternatif yinelemeli çözüm:

def myprint(d):
    stack = d.items()
    while stack:
        k, v = stack.pop()
        if isinstance(v, dict):
            stack.extend(v.iteritems())
        else:
            print("%s: %s" % (k, v))

2
Evet, ben böyle görünmesini hayal ettim. Teşekkürler. Öyleyse bunun avantajı, aşırı derin yuvalar için yığını taşmamasıdır? Yoksa başka bir şey mi var?
Niklas B.

@NiklasB .: evet, bu ilk fayda. Ayrıca, bu sürüm, yığını (a list) bir dequeveya hatta bir öncelik kuyruğu ile değiştirerek farklı geçiş sıralarına oldukça kolay bir şekilde uyarlanabilir .
Fred Foo

Evet, mantıklı. Teşekkürler ve mutlu kodlamalar :)
Niklas B.

Evet, ancak bu çözüm benimkinden ve yinelemeli çözümden daha fazla yer kaplıyor.
schlamar

1
@ ms4py: Eğlence için bir kıyaslama oluşturdum . Bilgisayarımda, yinelemeli sürüm en hızlıdır ve larsmans, üç test sözlüğünün tümü için ikinci sıradadır. Beklendiği gibi jeneratörler kullanılarak sürümü nispeten yavaştır (farklı jeneratör bağlamlarda ile hokkabazlık bir sürü yapmak zorunda olduğundan)
Niklas B.

10

Oraya ulaşma yolunda anahtarların kaydını tutan biraz farklı bir versiyon

def print_dict(v, prefix=''):
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            print_dict(v2, p2)
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            print_dict(v2, p2)
    else:
        print('{} = {}'.format(prefix, repr(v)))

Verilerinize yazdıracak

data['xml']['config']['portstatus']['status'] = u'good'
data['xml']['config']['target'] = u'1'
data['xml']['port'] = u'11'

Bu şekilde ihtiyacınız varsa, öneki bir dizi yerine bir anahtar dizisi olarak izlemek için onu değiştirmek de kolaydır.


Çıktı bir listeye nasıl eklenir?
2018, 12

5

İşte bunu yapmanın pitonik yolu. Bu işlev, tüm düzeylerde anahtar / değer çifti arasında geçiş yapmanıza olanak tanır. Her şeyi hafızaya kaydetmez, bunun yerine siz dönüp bakarken diktede yürür.

def recursive_items(dictionary):
    for key, value in dictionary.items():
        if type(value) is dict:
            yield (key, value)
            yield from recursive_items(value)
        else:
            yield (key, value)

a = {'a': {1: {1: 2, 3: 4}, 2: {5: 6}}}

for key, value in recursive_items(a):
    print(key, value)

Baskılar

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6

3

Scharron'un çözümüne dayalı listelerle çalışmak için alternatif bir çözüm

def myprint(d):
    my_list = d.iteritems() if isinstance(d, dict) else enumerate(d)

    for k, v in my_list:
        if isinstance(v, dict) or isinstance(v, list):
            myprint(v)
        else:
            print u"{0} : {1}".format(k, v)

2

Alternatif olarak yinelemeli çözüm:

def traverse_nested_dict(d):
    iters = [d.iteritems()]

    while iters:
        it = iters.pop()
        try:
            k, v = it.next()
        except StopIteration:
            continue

        iters.append(it)

        if isinstance(v, dict):
            iters.append(v.iteritems())
        else:
            yield k, v


d = {"a": 1, "b": 2, "c": {"d": 3, "e": {"f": 4}}}
for k, v in traverse_nested_dict(d):
    print k, v

Bu nasıl? Big O aynı olmalıdır ( O(depth)özyinelemeli çözüm için. Aynısı, doğru düşünüyorsam bu sürüm için de geçerlidir).
Niklas B.

"Yığını kopyalayın" mı? Neden bahsediyorsun? Her işlev çağrısı yeni bir yığın çerçevesi oluşturur. Çözümünüz itersaçık bir yığın olarak kullanıyor , bu nedenle Büyük-O bellek tüketimi aynı mı, yoksa bir şey mi kaçırıyorum?
Niklas B.

@NiklasB. Özyineleme her zaman ek yük ile gelir, ayrıntılar için Wikipedia'daki bu bölüme bakın: en.wikipedia.org/wiki/… Özyinelemeli çözümün yığın çerçevesi çok daha büyüktür.
schlamar

O paragrafı yanlış anlıyor olmalısın. İfadelerinizi destekleyecek hiçbir şey söylemiyor.
Niklas B.

1
@NiklasB. Hayır, çünkü buradaki yığın çerçevesi yalnızca yinelemedir ve özyinelemeli çözüm için yığın çerçevesi yineleme, program sayacı, değişken ortam vb.
İçerir

2

İç içe geçmiş bir sözlüğün tüm değerlerini yazdırmak için aşağıdaki kodu kullanıyorum, değerin sözlükleri içeren bir liste olabileceğini hesaba katıyorum. Bu, bir JSON dosyasını bir sözlüğe ayrıştırırken ve değerlerinden herhangi birinin olup olmadığını hızlıca kontrol etmem gerektiğinde yararlı oldu None.

    d = {
            "user": 10,
            "time": "2017-03-15T14:02:49.301000",
            "metadata": [
                {"foo": "bar"},
                "some_string"
            ]
        }


    def print_nested(d):
        if isinstance(d, dict):
            for k, v in d.items():
                print_nested(v)
        elif hasattr(d, '__iter__') and not isinstance(d, str):
            for item in d:
                print_nested(item)
        elif isinstance(d, str):
            print(d)

        else:
            print(d)

    print_nested(d)

Çıktı:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string

Burada çok benzer bir sorun var stackoverflow.com/questions/50642922/… . Sözlük listesinin son öğesini bulmanın, onu silmenin ve ardından bir üst seviyeye çıkmanın bir yolu var mı? Silemiyorsa, ben listeyi tersine çevirmek ve siliyorsunuz son öğe veri derinliği olduğu bir liste yapmak istiyorum
Heenashree Khandelwal

1

İşte Fred Foo'nun Python 2 için cevabının değiştirilmiş bir versiyonu. Orijinal yanıtta, sadece en derin iç içe geçme seviyesi çıktı. Anahtarları liste olarak çıkarırsanız, anahtarları tüm düzeyler için tutabilirsiniz, ancak bunlara başvurmak için bir liste listesine başvurmanız gerekir.

İşte fonksiyon:

def NestIter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in NestIter(value):
                yield [key, inner_key], inner_value
        else:
            yield [key],value

Anahtarlara başvurmak için:

for keys, vals in mynested: 
    print(mynested[keys[0]][keys[1][0]][keys[1][1][0]])

üç seviyeli bir sözlük için.

Birden çok anahtara erişmeden önce düzey sayısını bilmeniz gerekir ve düzey sayısı sabit olmalıdır (değerler arasında yineleme yaparken yuvalama düzeylerinin sayısını kontrol etmek için küçük bir komut dosyası eklemek mümkün olabilir, ancak ben yapmadım henüz buna baktı).


1

Bu yaklaşımı biraz daha esnek buluyorum, burada sadece anahtar, değer çiftlerini yayan ve listeler üzerinde yinelemek için kolayca genişletilebilen jeneratör işlevi sağlıyorsunuz.

def traverse(value, key=None):
    if isinstance(value, dict):
        for k, v in value.items():
            yield from traverse(v, k)
    else:
        yield key, value

Daha sonra kendi myprintfonksiyonunuzu yazabilir ve ardından bu anahtar-değer çiftlerini yazdırabilirsiniz.

def myprint(d):
    for k, v in traverse(d):
        print(f"{k} : {v}")

Bir test:

myprint({
    'xml': {
        'config': {
            'portstatus': {
                'status': 'good',
            },
            'target': '1',
        },
        'port': '11',
    },
})

Çıktı:

status : good
target : 1
port : 11

Bunu Python 3.6'da test ettim.


0

Bu yanıtlar yalnızca 2 düzey alt sözlük için işe yarar. Daha fazlası için şunu deneyin:

nested_dict = {'dictA': {'key_1': 'value_1', 'key_1A': 'value_1A','key_1Asub1': {'Asub1': 'Asub1_val', 'sub_subA1': {'sub_subA1_key':'sub_subA1_val'}}},
                'dictB': {'key_2': 'value_2'},
                1: {'key_3': 'value_3', 'key_3A': 'value_3A'}}

def print_dict(dictionary):
    dictionary_array = [dictionary]
    for sub_dictionary in dictionary_array:
        if type(sub_dictionary) is dict:
            for key, value in sub_dictionary.items():
                print("key=", key)
                print("value", value)
                if type(value) is dict:
                    dictionary_array.append(value)



print_dict(nested_dict)
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.