1
\$\begingroup\$

I want to change all values in a multidimensional dictionary. I have written this line of code, which does what I want it to do and I am trying to find out if this is the optimal way or just some convoluted solution I came up with:

 dict_data = dict([x,dict([y, dict_data[x][y]+100] for y in dict_data[x])] for x in dict_data)
\$\endgroup\$
3
\$\begingroup\$

If you're using both keys and values from a dictionary, then using the items method:

... for key, val in dct.items() ...

is neater than iterating over the keys and including dct[key] everywhere. In Python 2.7+, you can use a dictionary comprehension (see e.g. the tutorial) rather than pass a list comprehension to dict:

dict_data = {key: {key_: val_+100 for key_, val_ in val.items()} 
             for key, val in dict_data.items()}

Note that using key, val and the _ versions also makes it clearer what's happening than x and y.

\$\endgroup\$
  • \$\begingroup\$ You forgot to use items... \$\endgroup\$ – Stefan Pochmann May 11 '15 at 17:06
  • \$\begingroup\$ @StefanPochmann oh, for pity's... thank you! \$\endgroup\$ – jonrsharpe May 11 '15 at 17:07
  • 1
    \$\begingroup\$ You forgot it in the inner comprehension as well. C'mon, give up trying to fit it in one line :-P \$\endgroup\$ – Stefan Pochmann May 11 '15 at 17:11
  • \$\begingroup\$ Seeing it written with key and value i can easily see how this becomes more readable. Thanks for the explanations! \$\endgroup\$ – Guest20150511 May 12 '15 at 7:19
3
\$\begingroup\$

With comprehensions, which I generally love, I'd do it like jonrsharpe did. But here I'd find a loop much clearer:

for inner in dict_data.values():
    for key in inner:
        inner[key] += 100

Both can also be generalized to something more complex than a strictly two-dimensional dictionary, and again I find the loop version clearer (though less so):

def loop_add(dict_data):
    for inner in dict_data.values():
        for key in inner:
            inner[key] += 100

def comprehension_add(dict_data):
    # copied from jonrsharpe
    return {key: {key_: val_+100 for key_, val_ in val.items()}
            for key, val in dict_data.items()}

def rec_loop_add(dict_data):
    for key, value in dict_data.items():
        if isinstance(value, dict):
            rec_loop_add(value)
        else:
            dict_data[key] += 100

def rec_comp_add(dict_data):
    return {key: rec_comp_add(value) if isinstance(value, dict) else value+100
            for key, value in dict_data.items()}

A little speed test with the above functions and a 10x10 dictionary:

dict_data = {x: {y: x*y+10000000 for y in range(10)} for x in range(10)}
import copy, timeit
for func in loop_add, comprehension_add, rec_loop_add, rec_comp_add:
    cloned = copy.deepcopy(dict_data)
    seconds = timeit.timeit(lambda:func(cloned), number=100000)
    print('%6.3f seconds' % seconds, func.__name__)

Output:

 3.579 seconds loop_add
 4.735 seconds comprehension_add
11.598 seconds rec_loop_add
11.988 seconds rec_comp_add

For larger dictionaries, the difference shrinks. For a 100x100 dictionary (and 1000 runs):

 3.160 seconds loop_add
 3.695 seconds comprehension_add
 9.824 seconds rec_loop_add
 9.893 seconds rec_comp_add
\$\endgroup\$
  • \$\begingroup\$ I did start out writing the solution with a for loop (because that one i know how to write) but i did not realize that it would also be the 'faster' solution. My dictionary will be in the thousands keys range and it will be fun for me to see how these 4 solutions behave. Thanks for such detailed explanations (i did not know there were so many options to use). \$\endgroup\$ – Guest20150511 May 12 '15 at 7:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.