Vikas Chaudhary
Vikas Chaudhary

Reputation: 13

Traversing Down in a tree like dictionary in Python

I have a dictionary something like this:

{'A': [12343,
  2342349,
  {'B': [3423,
    342349283,
    73,
    {'C': [-23,
      -2342342,
      36],
     'D': [-2,
      -2520206,
      63]}],
   'E': [-1.5119711426000446,
    -1405627.5262916991,
    26.110728689275614,
    {'F': [-1.7211282679440503,
      -1601770.8149339128,
      113.9541439658396],
     'G': [0.21282003105839883,
      196143.28864221353,
      -13.954143965839597,
      {'H': [0.43384581412426826,
        399408,
        203],
       'I': [-0.22,
        -203265,
        -103]}]}]}]}

I want a function using which I can get values. example, traverse(dictionary,'F') and it should give me the output. Couldn't found any solution. I am able to traverse 1 or two level, but not more. Either the code will break or it will not stop.

My Current solution which is not working is:

def traverse(dictionary,search):
    print "Traversing"
    if isinstance(dictionary,dict):
        keys = dictionary.keys()
        print keys
        if search in keys:
            print "Found",search,"in",keys
            print "Printing found dict",dictionary
            print
            print "Check this out",dictionary.get(search)
            print "Trying to return"
            val=dictionary.get(search)
            return val
        else:
            for key in keys:
                print 'Key >>>>>>>>>',dictionary.get(key)
                print
                temp=dictionary.get(key)[-1]
                print "Temp >>>>>>>",temp
                traverse(temp,search)

Upvotes: 0

Views: 1178

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121834

You need to handle both dictionaries and lists to traverse your structure entirely. You currently only handle dictionaries, but the dictionary with the 'F' key in it is an element of a list object, so you can't find it with your method.

While you can use recursion to make use of the function call stack to track the different levels of your structure, I'd do it iteratively and use a list or collections.deque() (faster for this job) to do track the objects still to process. That's more efficient and won't run into recursion depth errors for larger structures.

For example, walking all the elements with a generator function, then yielding each element visited, could be:

from collections import deque

def walk(d):
    queue = deque([d])
    while queue:
        elem = queue.popleft()
        if isinstance(elem, dict):
            queue.extend(elem.values())
        elif isinstance(elem, list):
            queue.extend(elem)
        yield elem

The above uses a queue to process elements breath first; to use it as a stack, just replace queue.popleft() with queue.pop().

You can then use the above walker to find your elements:

def search_key(obj, key):
    for elem in walk(obj):
        if isinstance(elem, dict) and key in elem:
            return elem

For your dictionary, the above returns the first dictionary that contains the looked-for key:

>>> search_key(dictionary, 'F')
{'F': [-1.7211282679440503, -1601770.8149339128, 113.9541439658396], 'G': [0.21282003105839883, 196143.28864221353, -13.954143965839597, {'H': [0.43384581412426826, 399408, 203], 'I': [-0.22, -203265, -103]}]}
>>> _['F']
[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]

If you are only ever interested in the value for the given key, just return that, of course:

def search_key(obj, key):
    for elem in walk(obj):
        if isinstance(elem, dict) and key in elem:
            return elem[key]

Upvotes: 1

blhsing
blhsing

Reputation: 106543

Assuming there's going to be only one matching key in any of your given data structure, you can use a function that recursively traverses the dictionary looking for the key and returning its value if found, and if not found it will raise an exception, so that the calling frame can catch it and move on to the next candidate key:

def traverse(dictionary, search):
    for k, v in dictionary.items():
        if k == search:
            return v
        if isinstance(v[-1], dict):
            try:
                return traverse(v[-1], search)
            except ValueError:
                pass
    raise ValueError("Key '%s' not found" % search)

so that traverse(d, 'F') returns (assuming your dict is stored as variable d):

[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]

On the other hand, if there can be multiple matches in the given data, you can make the function yield the value of a matching key instead so that the function becomes a generator that generates sub-lists of 0 to many matching keys:

def traverse(dictionary, search):
    for k, v in dictionary.items():
        if k == search:
            yield v
        if isinstance(v[-1], dict):
            yield from traverse(v[-1], search)

so that list(traverse(d, 'F')) returns:

[[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]]

Upvotes: 1

Related Questions