theta
theta

Reputation: 25641

Iterate over nested dictionary

Is there an easy way of iterating over nested dictionary, which may consist of other objects like lists, tuples, then again dictionaries so that iteration covers all the elements of these other objects?

For example, if I type a key of a nested dictionary object, I would get it all listed in the Python interpreter.


[edit] here is example dictionary:

{
'key_1': 'value_1',
'key_2': {'key_21': [(2100, 2101), (2110, 2111)],
      'key_22': ['l1', 'l2'],
      'key_23': {'key_231': 'v'},
      'key_24': {'key_241': 502,
             'key_242': [(5, 0), (7, 0)],
             'key_243': {'key_2431': [0, 0],
                 'key_2432': 504,
                 'key_2433': [(11451, 0), (11452, 0)]
                },
             'key_244': {'key_2441': {'key_24411': {'key_244111': 'v_24411',
                                'key_244112': [(5549, 0)]
                               },
                          'key_24412':'v_24412'
                         },
                 'key_2441': ['ll1', 'll2']
                }
            },
     }
}

sorry for being unreadable, but I did the best that I could.

Upvotes: 20

Views: 29119

Answers (6)

edd313
edd313

Reputation: 1477

I took a small portion of your dictionary

d = {
    'key_1': 'value_1',
    'key_2': {'key_21': [(2100, 2101), (2110, 2111)]},
    'key_22': ['l1', 'l2'],
    'key_23': {'key_231': 'v'}
}

With a NestedDict it is straightforward to access all values.

>>> from ndicts.ndicts import NestedDict
>>> nd = NestedDict(d)
>>> list(nd.values())
['value_1', [(2100, 2101), (2110, 2111)], ['l1', 'l2'], 'v']

If you want to flatten this list you can use this recursive function

from typing import Iterable

def flatten(li):
    def wrap(x, result):
        if isinstance(x, Iterable) and type(x) is not str:
            for i in x:
                wrap(i, result)
        else:
            result.append(x)
    result = []
    wrap(li, result)
    return result
>>> flatten(list(nd.values()))
['value_1', 2100, 2101, 2110, 2111, 'l1', 'l2', 'v']

Upvotes: 0

NeilenMarais
NeilenMarais

Reputation: 3119

A generator version of Graddy's recurse() answer above that should not explode on strings, and also gives you the compound key (cookie crumb trail?) showing how you arrived at a certain value:

def recurse(d, keys=()):
    if type(d) == dict:
         for k in d:
            for rv in recurse(d[k], keys + (k, )):
                yield rv
    else:
        yield (keys, d)

for compound_key, val in recurse(eg_dict):
    print '{}: {}'.format(compound_key, val)

produces output (using the example dictionary provided in the question):

('key_1',): value_1
('key_2', 'key_21'): [(2100, 2101), (2110, 2111)]
('key_2', 'key_22'): ['l1', 'l2']
('key_2', 'key_23', 'key_231'): v
('key_2', 'key_24', 'key_241'): 502
('key_2', 'key_24', 'key_243', 'key_2433'): [(11451, 0), (11452, 0)]
('key_2', 'key_24', 'key_243', 'key_2432'): 504
('key_2', 'key_24', 'key_243', 'key_2431'): [0, 0]
('key_2', 'key_24', 'key_242'): [(5, 0), (7, 0)]
('key_2', 'key_24', 'key_244', 'key_2441'): ['ll1', 'll2']

In Python 3 the second yield loop should be replaceable with yield from. This generator could be made more general by replacing the type(d) == dict test with isinstance(d, collections.Mapping), using the Mapping ABC from the collections module.

Upvotes: 7

James Sapam
James Sapam

Reputation: 16950

Here is another solution,

#!/usr/bin/python

d = {'key_1': 'value_1',
     'key_2': {'key_21': [(2100, 2101), (2110, 2111)],
           'key_22': ['l1', 'l2'],
           'key_23': {'key_231': 'v'},
           'key_24': {'key_241': 502,
                      'key_242': [(5, 0), (7, 0)],
                      'key_243': {'key_2431': [0, 0],
                                  'key_2432': 504,
                                  'key_2433': [(11451, 0), (11452, 0)]},
                      'key_244': {'key_2441': ['ll1', 'll2']}}}}

def search_it(nested, target):
    found = []
    for key, value in nested.iteritems():
        if key == target:
            found.append(value)
        elif isinstance(value, dict):
            found.extend(search_it(value, target))
        elif isinstance(value, list):
            for item in value:
                if isinstance(item, dict):
                    found.extend(search_it(item, target))
        else:
            if key == target:
                found.append(value)
    return found

keys = [ 'key_242', 'key_243', 'key_242', 'key_244', 'key_1' ]

for key in keys:
    f = search_it(d, key)
    print 'Key: %s, value: %s' % (key, f[0])

Output:

Key: key_242, value: [(5, 0), (7, 0)]
Key: key_243, value: {'key_2433': [(11451, 0), (11452, 0)], 'key_2432': 504, 'key_2431': 
 [0, 0]}
Key: key_242, value: [(5, 0), (7, 0)]
Key: key_244, value: {'key_2441': ['ll1', 'll2']}
Key: key_1, value: value_1

Upvotes: 3

Graddy
Graddy

Reputation: 3008

def recurse(d):
  if type(d)==type({}):
    for k in d:
      recurse(d[k])
  else:
    print d

Upvotes: 19

Yugal Jindle
Yugal Jindle

Reputation: 45746

Iterating over a nested dictionary containing unexpected nested elements.

Here is my solution :

# d is the nested dictionary

for item in d:
    if type(item) == list:
        print "Item is a list"
        for i in item: print i
    elif type(item) == dict:
        print "Item is a dict"
        for i in item: print i
    elif type(item) == tuple:
        print "Item is a tuple"
        for i in item: print i
    else:
        print "Item is not a list, neither a dict and not even a tuple"
        print item

I think the above example is very general, you can mold it for your use case.

Upvotes: 2

Edmund
Edmund

Reputation: 10829

What about using a general-purpose wrapper generator, like the following:

def recursive(coll):
    """Return a generator for all atomic values in coll and its subcollections.
    An atomic value is one that's not iterable as determined by iter."""
    try:
        k = iter(coll)
        for x in k:
            for y in recursive(x):
                yield y
    except TypeError:
        yield coll


def test():
    t = [[1,2,3], 4, 5, [6, [7, 8], 9]]
    for x in recursive(t):
        print x

Upvotes: 0

Related Questions