Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively replace characters in a dictionary

How do I change all dots . to underscores (in the dict's keys), given an arbitrarily nested dictionary?

What I tried is write two loops, but then I would be limited to 2-level-nested dictionaries.

This ...

{
    "brown.muffins": 5,
    "green.pear": 4,
    "delicious.apples": {
        "green.apples": 2
    {
}

... should become:

{
    "brown_muffins": 5,
    "green_pear": 4,
    "delicious_apples": {
        "green_apples": 2
    {
}

Is there an elegant way?

like image 343
Xiphias Avatar asked Jan 05 '16 15:01

Xiphias


2 Answers

You can write a recursive function, like this

from collections.abc import Mapping
def rec_key_replace(obj):
    if isinstance(obj, Mapping):
        return {key.replace('.', '_'): rec_key_replace(val) for key, val in obj.items()}
    return obj

and when you invoke this with the dictionary you have shown in the question, you will get a new dictionary, with the dots in keys replaced with _s

{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

Explanation

Here, we just check if the current object is an instance of dict and if it is, then we iterate the dictionary, replace the key and call the function recursively. If it is actually not a dictionary, then return it as it is.

like image 168
thefourtheye Avatar answered Oct 05 '22 14:10

thefourtheye


Assuming . is only present in keys and all the dictionary's contents are primitive literals, the really cheap way would be to use str() or repr(), do the replacement, then ast.literal_eval() to get it back:

d ={
    "brown.muffins": 5,
    "green.pear": 4,
    "delicious_apples": {
        "green.apples": 2
    } # correct brace
}

Result:

>>> import ast
>>> ast.literal_eval(repr(d).replace('.','_'))
{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

If the dictionary has . outside of keys, we can replace more carefully by using a regular expression to look for strings like 'ke.y': and replace only those bits:

>>> import re
>>> ast.literal_eval(re.sub(r"'(.*?)':", lambda x: x.group(0).replace('.','_'), repr(d)))
{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

If your dictionary is very complex, with '.' in values and dictionary-like strings and so on, use a real recursive approach. Like I said at the start, though, this is the cheap way.

like image 29
TigerhawkT3 Avatar answered Oct 05 '22 13:10

TigerhawkT3