Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 286
Yannick Avatar asked Sep 10 '18 15:09

Yannick


2 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
 
like image 158
user2390182 Avatar answered Nov 09 '22 00:11

user2390182


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)

like image 45
Patrick Haugh Avatar answered Nov 08 '22 22:11

Patrick Haugh