Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively replace dictionary values with matching key

I'm trying to take a dictionary and find all of the keys that match key and replace their value with replace_value. The dictionaries can in theory be infinitely deep, so it must be done recursively.

My current solution replaces values correctly, but raises an exception saying "maximum recursion depth exceeded while calling a Python object" (not to mention the fact that it's poor use of recursion with no return values).

def replace_item(obj, key, replace_value):
    """
    Replaces the dictionary value of key with replace_value in the obj dictionary.
    """
    if key in obj:
        obj[key] = replace_value
    
    for k, v in obj.items():
        if isinstance(v, dict):
            item = replace_item(v, key, replace_value)

            if item is not None:
                item = replace_value

     return obj

An example of an operation it would perform would be the following:

Original Dictionary

person_dict = {
    "name": "Alex",
    "sex": "M",
    "title": "Engineer",
    "misc": {
        "mailbox": "3A",
        "work_type": "remote"
    }
}

Then I'd make a call to replace_item(person_dict, "work_type", "office"), which I'd preferably like to change over to returning the updated dictionary (person_dict = replace_item(person_dict, "work_type", "office")).

Replaced Value Dictionary

person_dict = {
    "name": "Alex",
    "sex": "M",
    "title": "Engineer"
    "misc": {
        "mailbox": "3A",
        "work_type": "office"
    }
}

How can I go about fixing my recursion?

like image 378
swiftsly Avatar asked Dec 24 '22 15:12

swiftsly


2 Answers

You have some strange behavior where you are expecting a return but you don't have one. Also your description implies it should replace nested keys, but your code you will miss when a dictionary at the top level does not have the key but at a lower level does. I believe the below code accomplishes what you described:

def replace_item(obj, key, replace_value):
    for k, v in obj.items():
        if isinstance(v, dict):
            obj[k] = replace_item(v, key, replace_value)
    if key in obj:
        obj[key] = replace_value
    return obj

EDIT: As @dashiell suggested, moving the top level reassignment after the recursive search/replace avoids the infinite recursion trap of having key exist in the replace_value.

like image 122
Farmer Joe Avatar answered Dec 28 '22 15:12

Farmer Joe


Here's a functional-style take:

def replace(obj, key, val):
    return {k: replace(val if k == key else v, key, val) 
        for k,v in obj.items()} if isinstance(obj, dict) else obj

It's not efficient in Python (since all values/subdicts are re-created), but demonstrates how to solve your problem without side-effects and without mutating objects.

like image 32
randomir Avatar answered Dec 28 '22 15:12

randomir