Recursively iterate through a nested dict and return value of the first matching key

Viewed 5864

I have a deeply nested dict and need to iterate through it and return the value corresponding to the key argument, second argument of my function.

For example, with

tree = {"a": 12, "g":{ "b": 2, "c": 4}, "d":5}

tree_traverse(tree, "d") should return 5

Here is my code:

def tree_traverse(tree, key):
    for k,v  in tree.items():
        if isinstance(v, dict):
            tree_traverse(v, key)

        elif k == key:
            return v

The problem I have is that this function returns None if it doesnt find the matching key once it's done iterating through the deepest nested dict. I don't want it to return anything before the matching key is found.

I didn't find a solution in another thread, most of them use print statements and don't return anything so I guess it avoids this issue.

3 Answers

You have to check whether the recursive call actually found something so you can continue the loop. E.g. try the following:

def tree_traverse(tree, key):
    if key in tree:
        return tree[key]
    for v in filter(dict.__instancecheck__, tree.values()):
        if (found := tree_traverse(v, key)) is not None:  
            return found
 

Here we instantiate an object when the function is created, that all executions of the function will share, called _marker. We return this object if we don't find the key. (You could also use None here, but None is frequently a meaningful value.)

def tree_traverse(tree, key, *, _marker=object()):
    for k,v  in tree.items():
        if isinstance(v, dict):
            res = tree_traverse(v, key, _marker=_marker)
            if res is not _marker:
                return res
        elif k == key:
            return v
    return _marker

def find(tree, key):
    _marker = object()
    res = tree_traverse(tree, key, _marker=_marker)
    if res is _marker:
        raise KeyError("Key {} not found".format(key))
    return res

I use tree_traverse as a helper function because we want different behaviour at the outermost layer of our recursion (throw an error) than we want inside (return a _marker object)

A NestedDict can solve the problem

from ndicts.ndicts import NestedDict

def tree_traverse(tree, k):
    nd = NestedDict(tree)
    for key, value in nd.items():
        if k in key:
            return value
>>> tree = {"a": 12, "g":{ "b": 2, "c": 4}, "d":5}
>>> tree_traverse(tree, "d")
5

To install ndicts pip install ndicts

Related