Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More pythonic way to change multilevel dictionary values

For example, I have the following dict:

{'foo': 'test', 
 'bar': {'test1': 'some text here'},
 'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}}}

Or something like this, maybe more levels.

Now the question is, I want to change...'some text here' to 'A test'. Yeah, I can use a lot of for loops like the following code:

d = {'foo': 'test',
     'bar': {'test1': 'some text here'},
     'baz': {'test2': {'test3': 'some text here',
                       'test4': 'some text here'}}}

for i in d:
    if d[i] == 'some text here':
        d[i] = 'A test'

    elif type(d[i]) == dict:
        for j in d[i]:
            if d[i][j] == 'some text here':
                d[i][j] = 'A test'

            elif type(d[i][j]) == dict:
                for n in d[i][j]:
                    if d[i][j][n] == 'some text here':
                        d[i][j][n] = 'A test'

__import__('pprint').pprint(d)

Output:

{'bar': {'test1': 'A test'},                                                    
 'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
 'foo': 'test'}

However I don't think this is a good way...Any ideas?

like image 974
Remi Crystal Avatar asked Sep 06 '26 10:09

Remi Crystal


2 Answers

This looks like a good case for recursion.

import re

def replace_rec(data, search, replace, _pattern=None):
    if _pattern is None:
        _pattern = re.compile(r'^%s$' % search)
    for k, v in data.items():
        try:
            data[k] = _pattern.sub(replace, v)
        except TypeError:
            try:
                replace_rec(data[k], search, replace, _pattern=_pattern)
            except AttributeError:
                # Leave any other types as they are.
                continue

Use it for your example like this:

>>> data = {
...     'foo': 'test',
...     'bar': {'test1': 'some text here'},
...     'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}},
...     'loc': [1, 2, 3],
...     'fp': 'foo some text here foo',
... }
>>> replace_rec(data, 'some text here', 'A test')
>>> pprint.pprint(data)
{'bar': {'test1': 'A test'},
 'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
 'foo': 'test',
 'fp': 'foo some text here foo',
 'loc': [1, 2, 3]}
like image 61
Justin Fay Avatar answered Sep 08 '26 17:09

Justin Fay


A slightly alternative version. This correctly handles non-strings in your dictionaries, and only replaces an exact text match.

def replace(d, find_text, replace_text):
    for k, v in d.items():
        if isinstance(v, dict):
            replace(v, find_text, replace_text)
        elif isinstance(v, str):
            if v == find_text:
                d[k] = replace_text

d = {
    'test': 'dont change some text here',
    'ignore' : 42,

    'foo': 'test', 
    'bar': {'test1': 'some text here'},
    'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}}}       

replace(d, 'some text here', 'A test')

__import__('pprint').pprint(d)

This would display:

{'bar': {'test1': 'A test'},
 'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
 'foo': 'test',
 'ignore': 42,
 'test': 'dont change some text here'}
like image 44
Martin Evans Avatar answered Sep 08 '26 18:09

Martin Evans



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!