Skip to content Skip to sidebar Skip to footer

Python: Apply Function To Values In Nested Dictionary

I have an arbitrarily deep set of nested dictionary: x = {'a': 1, 'b': {'c': 6, 'd': 7, 'g': {'h': 3, 'i': 9}}, 'e': {'f': 3}} and I'd like to basically apply a function to all th

Solution 1:

Visit all nested values recursively:

import collections

def map_nested_dicts(ob, func):if isinstance(ob, collections.Mapping):
        return {k: map_nested_dicts(v, func) fork, vinob.iteritems()}
    else:
        returnfunc(ob)

map_nested_dicts(x, lambda v: v + 7)
# Creates a new dict object:
#    {'a': 8, 'b': {'c': 13, 'g': {'h': 10, 'i': 16}, 'd': 14}, 'e': {'f': 10}}

In some cases it's desired to modify the original dict object (to avoid re-creating it):

import collections

def map_nested_dicts_modify(ob, func):for k, v in ob.iteritems():
        if isinstance(v, collections.Mapping):
            map_nested_dicts_modify(v, func)else:
            ob[k] = func(v)

map_nested_dicts_modify(x, lambda v: v + 7)
# x is now
#    {'a': 8, 'b': {'c': 13, 'g': {'h': 10, 'i': 16}, 'd': 14}, 'e': {'f': 10}}

If you're using Python 3:

  • replace dict.iteritems with dict.items

  • replace import collections with import collections.abc

  • replace collections.Mapping with collections.abc.Mapping

Solution 2:

Just to expand on vaultah's answer, if one of your elements can be a list, and you'd like to handle those too:

import collections

def map_nested_dicts_modify(ob, func):for k, v in ob.iteritems():
    if isinstance(v, collections.Mapping):
        map_nested_dicts_modify(v, func)
    elif isinstance(v, list):
        ob[k] = map(func, v)else:
        ob[k] = func(v)

Solution 3:

If you need it to work for both lists and dicts in arbitrary nesting:

defapply_recursive(func, obj):
    ifisinstance(obj, dict):  # if dict, apply to each keyreturn {k: apply_recursive(func, v) for k, v in obj.items()}
    elifisinstance(obj, list):  # if list, apply to each elementreturn [apply_recursive(func, elem) for elem in obj]
    else:
        return func(obj)

Solution 4:

I have a more general implementation that can accept any number of containers of any type as parameters.

from collections.abc import Iterable
import types
defdict_value_map(fun, *dicts):
    keys = dicts[0].keys()
    for d in dicts[1:]:
        assert d.keys() == keys
    return {k:fun(*(d[k] for d in dicts)) for k in keys}
defcollection_map(fun, *collections):
    assertlen(collections) > 0ifisinstance(collections[0], dict):
        return dict_value_map(fun, *collections)
    ifisinstance(collections[0], (tuple, list, set)):
        returntype(collections[0])(map(fun, *collections))
    else:
        returnmap(fun, *collections)
iscollection = lambda v:(isinstance(v,Iterable)and(notisinstance(v,str)))

defapply(fun, *collections, at=lambda collections: not iscollection(collections[0])):
    '''
    like standard map, but can apply the fun to inner elements.
    at: a int, a function or sometype. 
    at = 0 means fun(*collections)
    at = somefunction. fun will applied to the elements when somefunction(elements) is True
    at = sometype. fun will applied to the elements when elements are sometype.
    '''ifisinstance(at, int):
        assert at >= 0if at == 0:
            return fun(*collections)
        else:
            return collection_map(lambda *cs:apply(fun, *cs, at=at-1), *collections)
    ifisinstance(at, types.FunctionType):
        if at(collections):
            return fun(*collections)
        else:
            return collection_map(lambda *cs:apply(fun, *cs, at=at), *collections)
    else:
        return apply(fun, *collections, at=lambda eles:isinstance(eles[0], at))

examples:

> apply(lambda x:2*x, [(1,2),(3,4)])  
[(2, 4), (6, 8)]

> apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]))
([6, 8], [10, 12])

> apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]), at=1)  
([1, 2, 5, 6], [3, 4, 7, 8])

> apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]), at=0)  
([1, 2], [3, 4], [5, 6], [7, 8])

> apply(lambda a,b:a+b, {'m':[(1,2),[3,{4}]], 'n':5}, {'m':[(6,7),[8,{9}]],'n':10})  
{'m': [(7, 9), [11, {13}]], 'n': 15}

> apply(str.upper, [('a','b'),('c','d')], at=str)  
[('A', 'B'), ('C', 'D')]

and

> apply(lambda v:v+7, {'a': 1, 'b': {'c': 6, 'd': 7, 'g': {'h': 3, 'i': 9}}, 'e': {'f': 3}})
{'a': 8, 'b': {'c': 13, 'd': 14, 'g': {'h': 10, 'i': 16}}, 'e': {'f': 10}}

Post a Comment for "Python: Apply Function To Values In Nested Dictionary"