Search code examples
pythonfunctiondictionarygeneratoriterable

How to iterate through an N-level nested dictionary in Python?


I find myself making multilevel dictionaries quite a bit. I always have to write very verbose code to iterate through all the levels of the dictionaries with a lot of temporary variables.

Is there a way to generalize this function to iterate through multiple levels instead of hardcoding in and manually specifying how many levels there are?

def iterate_multilevel_dictionary(d, number_of_levels):
    # How to auto-detect number of levels? 
    # number_of_levels = 0
    if number_of_levels == 1:
        for k1, v1 in d.items():
            yield k1, v1
    if number_of_levels == 2:
        for k1, v1 in d.items():
            for k2, v2 in v1.items():
                yield k1, k2, v2
    if number_of_levels == 3:
        for k1, v1 in d.items():
            for k2, v2 in v1.items():
                for k3, v3 in v2.items():
                    yield k1, k2, k3, v3
                    
# Level 1
d_level1 = {"a":1,"b":2,"c":3}
for items in iterate_multilevel_dictionary(d_level1, number_of_levels=1):
    print(items)
# ('a', 1)
# ('b', 2)
# ('c', 3)

# Level 2
d_level2 = {"group_1":{"a":1}, "group_2":{"b":2,"c":3}}
for items in iterate_multilevel_dictionary(d_level2, number_of_levels=2):
    print(items)
#('group_1', 'a', 1)
#('group_2', 'b', 2)
#('group_2', 'c', 3)

# Level 3
d_level3 = {"collection_1":d_level2}
for items in iterate_multilevel_dictionary(d_level3, number_of_levels=3):
    print(items)
# ('collection_1', 'group_1', 'a', 1)
# ('collection_1', 'group_2', 'b', 2)
# ('collection_1', 'group_2', 'c', 3)

Solution

  • try out this code

    it also supports a combination of levels

    from typing import List, Tuple
    
    
    def iterate_multilevel_dictionary(d: dict):
        dicts_to_iterate: List[Tuple[dict, list]] = [(d, [])]
        '''
        the first item is the dict object and the second object is the prefix keys 
        '''
        while dicts_to_iterate:
            current_dict, suffix = dicts_to_iterate.pop()
            for k, v in current_dict.items():
                if isinstance(v, dict):
                    dicts_to_iterate.append((v, suffix + [k]))
                else:
                    yield suffix + [k] + [v]
    
    
    if __name__ == '__main__':
        d_level1 = {"a": 1, "b": 2, "c": 3}
        print(f"test for {d_level1}")
        for items in iterate_multilevel_dictionary(d_level1):
            print(items)
        d_level2 = {"group_1": {"a": 1}, "group_2": {"b": 2, "c": 3}}
        print(f"test for {d_level2}")
        for items in iterate_multilevel_dictionary(d_level2):
            print(items)
    
        d_level3 = {"collection_1": d_level2}
        print(f"test for {d_level3}")
        for items in iterate_multilevel_dictionary(d_level3):
            print(items)
    
        d_level123 = {}
        [d_level123.update(i) for i in [d_level1, d_level2, d_level3]]
        print(f"test for {d_level123}")
        for items in iterate_multilevel_dictionary(d_level123):
            print(items)
    

    the outputs is:

    test for {'a': 1, 'b': 2, 'c': 3}
    ['a', 1]
    ['b', 2]
    ['c', 3]
    test for {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}
    ['group_2', 'b', 2]
    ['group_2', 'c', 3]
    ['group_1', 'a', 1]
    test for {'collection_1': {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}}
    ['collection_1', 'group_2', 'b', 2]
    ['collection_1', 'group_2', 'c', 3]
    ['collection_1', 'group_1', 'a', 1]
    test for {'a': 1, 'b': 2, 'c': 3, 'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}, 'collection_1': {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}}
    ['a', 1]
    ['b', 2]
    ['c', 3]
    ['collection_1', 'group_2', 'b', 2]
    ['collection_1', 'group_2', 'c', 3]
    ['collection_1', 'group_1', 'a', 1]
    ['group_2', 'b', 2]
    ['group_2', 'c', 3]
    ['group_1', 'a', 1]
    

    using recursion is another approach but I thought writing without recursion is more challenging and more efficient :)