วนซ้ำค่าพจนานุกรมที่ซ้อนกันทั้งหมดหรือไม่


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

ฉันพยายามวนผ่านพจนานุกรมและพิมพ์คู่ค่าคีย์ทั้งหมดโดยที่ค่านั้นไม่ใช่พจนานุกรมที่ซ้อนกัน หากค่าเป็นพจนานุกรมฉันต้องการป้อนและพิมพ์คู่ค่าคีย์ของมัน ... ฯลฯ ความช่วยเหลือใด ๆ

แก้ไข

แล้วเรื่องนี้ล่ะ? มันยังคงพิมพ์เพียงสิ่งเดียว

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

กรณีทดสอบแบบเต็ม

พจนานุกรม:

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

ผลลัพธ์:

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

1
ดูเหมือนคุณต้องการเรียกซ้ำ แต่คำอธิบายไม่ชัดเจนพอที่จะตรวจสอบได้ แล้วตัวอย่าง in- / output ล่ะ? นอกจากนี้รหัสของคุณมีอะไรผิดปกติ
Niklas B.

2
มีขีด จำกัด การเรียกซ้ำแบบคงที่ใน Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke

2
@ Jan-PhilipGehrcke: การใช้อัลกอริทึมบนโครงสร้างข้อมูลแบบต้นไม้โดยไม่มีการเรียกซ้ำเป็นการฆ่าตัวตายธรรมดา
Niklas B.

2
@ ทักคุน: คุณใช้dictเป็นชื่อตัวแปร อย่าทำสิ่งนี้ (นี่คือสาเหตุที่ล้มเหลว)
Niklas B.

3
@NiklasB. เรื่อง "การฆ่าตัวตาย": ฉันเพิ่งใช้อัลกอริทึมของ Scharron แบบวนซ้ำและมีเพียงสองบรรทัดที่ยาวขึ้นและยังทำตามได้ง่าย นอกจากนี้การแปลการเรียกซ้ำเป็นการทำซ้ำมักเป็นข้อกำหนดเมื่อเปลี่ยนจากต้นไม้เป็นกราฟทั่วไป
Fred Foo

คำตอบ:


158

ตามที่ Niklas กล่าวไว้คุณต้องมีการเรียกซ้ำเช่นคุณต้องการกำหนดฟังก์ชันเพื่อพิมพ์คำสั่งของคุณและถ้าค่าเป็นคำสั่งคุณต้องการเรียกใช้ฟังก์ชันการพิมพ์ของคุณโดยใช้คำสั่งใหม่นี้

สิ่งที่ต้องการ :

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

3
การปรับปรุงเล็กน้อย เพิ่มพิมพ์ (k) ก่อนเรียก myprint (v)
Naomi Fridman

ด้วยการเรียกซ้ำมันเป็นเรื่องเล็กน้อย
sergzach

37

มีปัญหาที่อาจเกิดขึ้นหากคุณเขียนการใช้งานแบบวนซ้ำของคุณเองหรือเทียบเท่ากับสแต็ก ดูตัวอย่างนี้:

    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

ตามปกติพจนานุกรมที่ซ้อนกันจะเป็นต้นไม้ n-nary เหมือนโครงสร้างข้อมูล แต่คำจำกัดความไม่ได้รวมถึงความเป็นไปได้ของการข้ามขอบหรือแม้แต่ขอบด้านหลัง (ดังนั้นจึงไม่ใช่ต้นไม้อีกต่อไป) ตัวอย่างเช่นที่นี่key2.2ถือไปยังพจนานุกรมจากkey1 , key2.3ชี้ไปยังพจนานุกรมทั้งหมด (back edge / cycle) เมื่อมีขอบด้านหลัง (รอบ) สแตก / การเรียกซ้ำจะทำงานไม่สิ้นสุด

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

หากคุณพิมพ์พจนานุกรมนี้ด้วยการใช้งานจากScharron

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

คุณจะเห็นข้อผิดพลาดนี้:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

เช่นเดียวกันกับการใช้งานจากผู้ส่ง senderle

ในทำนองเดียวกันคุณจะได้รับการวนซ้ำที่ไม่มีที่สิ้นสุดด้วยการใช้งานนี้จากFred Foo :

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

อย่างไรก็ตาม Python ตรวจพบวัฏจักรในพจนานุกรมที่ซ้อนกัน:

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

"{ ... }"คือที่ที่ตรวจพบวงจร

ตามที่Moondraร้องขอนี่เป็นวิธีหลีกเลี่ยงรอบ (DFS):

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)

แล้วคุณจะใช้วิธีแก้ปัญหาซ้ำได้อย่างไร?
dreftymac

2
@dreftymac ฉันจะเพิ่มชุดที่เยี่ยมชมสำหรับคีย์เพื่อหลีกเลี่ยงการวนรอบ: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)
tengr

1
ขอบคุณที่ชี้ให้เห็น คุณช่วยกรุณาใส่รหัสของคุณในคำตอบด้วย ฉันคิดว่ามันตอบโจทย์ของคุณได้อย่างสมบูรณ์
Moondra

สำหรับ Python3 ให้ใช้list(d.items())เป็นการd.items()ส่งคืนมุมมองไม่ใช่รายการและใช้v.items()แทนv.iteritems()
สูงสุด

33

เนื่องจากdictสามารถทำซ้ำได้คุณสามารถใช้สูตรที่ทำซ้ำคอนเทนเนอร์ที่ซ้อนกันแบบคลาสสิกกับปัญหานี้ได้โดยมีการเปลี่ยนแปลงเล็กน้อยเพียงไม่กี่รายการ นี่คือ Python 2 เวอร์ชัน (ดูด้านล่างสำหรับ 3):

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

ทดสอบ:

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 อาจเป็นไปได้ที่จะสร้างไฟล์Mappingที่มีคุณสมบัติเป็นMappingแต่ไม่ได้มีiteritemsซึ่งในกรณีนี้จะล้มเหลว เอกสารไม่ได้ระบุว่าiteritemsจำเป็นสำหรับMapping; ในทางกลับกันแหล่งข้อมูลให้Mappingประเภทiteritemsวิธีการ ดังนั้นสำหรับประเพณีMappingsสืบทอดจากcollections.Mappingกรณีที่ชัดเจน

ใน Python 3 มีการปรับปรุงหลายอย่าง ใน Python 3.3 คลาสพื้นฐานแบบนามธรรมอาศัยอยู่ในcollections.abc. พวกเขายังคงอยู่ในcollectionsเกินไปสำหรับความเข้ากันได้แบบย้อนกลับ แต่ดีกว่าที่มีคลาสฐานนามธรรมของเรารวมกันในเนมสเปซเดียว ดังนั้นสิ่งนี้นำเข้าabcจากcollections. นอกจากนี้ Python 3.3 ยังเพิ่มyield fromซึ่งออกแบบมาสำหรับสถานการณ์ประเภทนี้เท่านั้น นี่ไม่ใช่น้ำตาลวากยสัมพันธ์ที่ว่างเปล่า อาจทำให้โค้ดเร็วขึ้นและการมีปฏิสัมพันธ์ที่เหมาะสมมากขึ้นด้วยcoroutines

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)hasattr(item, "iteritems")รับประกันไม่มี การตรวจสอบcollections.Mappingจะดีกว่า
Fred Foo

1
@larsmans คุณพูดถูกแน่นอน ฉันคิดว่าการใช้Iterableจะทำให้โซลูชันนี้มีความครอบคลุมมากขึ้นโดยลืมไปว่ามันไม่จำเป็นต้องมีiteritemsซ้ำ
ส่ง

+1 สำหรับคำตอบนี้เนื่องจากเป็นวิธีแก้ปัญหาทั่วไปที่ใช้ได้กับปัญหานี้ แต่ไม่ได้ จำกัด เพียงการพิมพ์ค่าเท่านั้น @ ทักคุนคุณควรพิจารณาตัวเลือกนี้อย่างแน่นอน ในระยะยาวคุณจะต้องการมากกว่าแค่พิมพ์ค่า
Alejandro Piad

1
@ Seanny123 ขอบคุณที่ดึงดูดความสนใจของฉันมาที่นี่ Python 3 เปลี่ยนรูปภาพได้หลายวิธีในความเป็นจริง - ฉันจะเขียนสิ่งนี้ใหม่เป็นเวอร์ชันที่ใช้yield fromไวยากรณ์ใหม่
ส่ง

25

ทางเลือกในการแก้ปัญหาซ้ำ:

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
ใช่นั่นคือสิ่งที่ฉันจินตนาการให้ดูเหมือน ขอบคุณ ข้อดีของสิ่งนี้คือมันจะไม่ล้นสแต็กสำหรับการทำรังที่ลึกมาก? หรือมีอย่างอื่นอีกหรือไม่
Niklas B.

@NiklasB: ใช่นั่นคือประโยชน์แรก นอกจากนี้เวอร์ชันนี้ยังสามารถปรับให้เข้ากับคำสั่งการข้ามผ่านที่แตกต่างกันได้อย่างง่ายดายโดยการแทนที่สแต็ก (a list) ด้วยdequeคิวลำดับความสำคัญ
Fred Foo

ใช่เข้าท่า ขอบคุณและมีความสุขในการเขียนโค้ด :)
Niklas B.

ใช่ แต่โซลูชันนี้ใช้พื้นที่มากกว่าของฉันและแบบวนซ้ำ
schlamar

1
@ ms4py: เพื่อความสนุกสนานผมสร้างมาตรฐาน ในคอมพิวเตอร์ของฉันเวอร์ชันเรียกซ้ำเร็วที่สุดและ larsmans เป็นที่สองสำหรับพจนานุกรมทดสอบทั้งสาม เวอร์ชันที่ใช้เครื่องกำเนิดไฟฟ้าค่อนข้างช้าอย่างที่คาดไว้ (เนื่องจากต้องเล่นกลกับบริบทของเครื่องกำเนิดไฟฟ้าที่แตกต่างกันมาก)
Niklas B.

10

เวอร์ชันที่แตกต่างกันเล็กน้อยที่ฉันเขียนซึ่งติดตามคีย์ตลอดทางเพื่อไปที่นั่น

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

ข้อมูลของคุณจะพิมพ์ออกมา

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

นอกจากนี้ยังง่ายต่อการปรับเปลี่ยนเพื่อติดตามคำนำหน้าเป็นทูเพิลของคีย์แทนที่จะเป็นสตริงหากคุณต้องการด้วยวิธีนั้น


จะเพิ่มผลลัพธ์ลงในรายการได้อย่างไร?
Shash

5

นี่คือวิธี pythonic ที่จะทำ ฟังก์ชันนี้จะช่วยให้คุณสามารถวนซ้ำคู่คีย์ - ค่าในทุกระดับ มันไม่ได้บันทึกสิ่งทั้งหมดลงในหน่วยความจำ แต่เป็นการเดินผ่านคำสั่งในขณะที่คุณวนลูป

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)

พิมพ์

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

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

วิธีแก้ซ้ำเป็นทางเลือก:

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

นั้นเป็นอย่างไร? Big O ควรจะเหมือนกัน ( O(depth)สำหรับโซลูชันแบบวนซ้ำเช่นเดียวกันกับเวอร์ชันนี้หากฉันคิดถูกต้อง)
Niklas B.

“ คัดลอกกอง”? คุณกำลังพูดถึงอะไร? ทุกการเรียกใช้ฟังก์ชันจะสร้าง stackframe ใหม่ โซลูชันของคุณใช้itersเป็นสแต็กอย่างชัดเจนดังนั้นการใช้หน่วยความจำ Big-O จึงเท่ากันหรือฉันขาดอะไรไป?
Niklas B.

@NiklasB การเรียกซ้ำมักมาพร้อมกับค่าใช้จ่ายโปรดดูรายละเอียดในส่วนนี้ที่ Wikipedia: en.wikipedia.org/wiki/…กรอบสแตกของโซลูชันการเรียกซ้ำนั้นใหญ่กว่ามาก
schlamar

คุณต้องเข้าใจผิดในย่อหน้านั้น มันไม่ได้พูดอะไรเพื่อสนับสนุนงบของคุณ
Niklas B.

1
@NiklasB ไม่เพราะสแต็กเฟรมในที่นี้เป็นเพียง iter และสำหรับโซลูชันแบบวนซ้ำสแต็กเฟรมมีตัวนับโปรแกรมตัวนับสภาพแวดล้อมตัวแปร ฯลฯ ...
schlamar

2

ฉันใช้รหัสต่อไปนี้เพื่อพิมพ์ค่าทั้งหมดของพจนานุกรมที่ซ้อนกันโดยคำนึงว่าค่านั้นอาจเป็นรายการที่มีพจนานุกรม สิ่งนี้มีประโยชน์สำหรับฉันเมื่อแยกวิเคราะห์ไฟล์ JSON ลงในพจนานุกรมและจำเป็นต้องตรวจสอบอย่างรวดเร็วว่ามีค่า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)

เอาท์พุท:

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

ผมมีปัญหาที่คล้ายกันมากที่นี่stackoverflow.com/questions/50642922/... มีวิธีค้นหาองค์ประกอบสุดท้ายของรายการพจนานุกรมลบสิ่งนั้นแล้วเลื่อนระดับขึ้นหรือไม่? ถ้าไม่ลบฉันต้องการสร้างรายการที่องค์ประกอบสุดท้ายคือความลึกของข้อมูลดังนั้นฉันจึงย้อนกลับรายการและลบ
Heenashree Khandelwal

1

นี่คือคำตอบของ Fred Foo เวอร์ชันแก้ไขสำหรับ Python 2 ในการตอบกลับดั้งเดิมจะมีการส่งออกเฉพาะระดับการซ้อนที่ลึกที่สุดเท่านั้น หากคุณส่งออกคีย์เป็นรายการคุณสามารถเก็บคีย์สำหรับทุกระดับได้แม้ว่าจะอ้างอิงถึงคีย์เหล่านั้นคุณจำเป็นต้องอ้างอิงรายการของรายการ

นี่คือฟังก์ชั่น:

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

ในการอ้างอิงคีย์:

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

สำหรับพจนานุกรมสามระดับ

คุณต้องทราบจำนวนระดับก่อนที่จะเข้าถึงหลายคีย์และจำนวนระดับควรคงที่ (อาจเป็นไปได้ที่จะเพิ่มสคริปต์เล็กน้อยเพื่อตรวจสอบจำนวนระดับการซ้อนกันเมื่อทำซ้ำตามค่า แต่ฉันไม่ได้ ยังดูสิ่งนี้)


1

ฉันพบว่าวิธีนี้ยืดหยุ่นกว่าเล็กน้อยที่นี่คุณเพียงแค่ให้ฟังก์ชันเครื่องกำเนิดไฟฟ้าที่ปล่อยคีย์คู่ค่าและสามารถขยายได้อย่างง่ายดายเพื่อทำซ้ำในรายการ

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

จากนั้นคุณสามารถเขียนmyprintฟังก์ชันของคุณเองจากนั้นจะพิมพ์คู่ค่าคีย์เหล่านั้น

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

การทดสอบ:

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

เอาท์พุท:

status : good
target : 1
port : 11

ฉันทดสอบสิ่งนี้กับ Python 3.6


0

คำตอบเหล่านี้ใช้ได้กับพจนานุกรมย่อย 2 ระดับเท่านั้น หากต้องการข้อมูลเพิ่มเติมโปรดลองสิ่งนี้:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.