我有一套任意深度的嵌套字典:
x = {'a': 1, 'b': {'c': 6, 'd': 7, 'g': {'h': 3, 'i': 9}}, 'e': {'f': 3}}
我想基本上将一个函数应用于字典中的所有整数,所以就像map
一样,我猜,但对于嵌套字典。
所以:map_nested_dicts(x, lambda v: v + 7)
就是那种目标。
我坚持使用最好的方法来存储键层,然后将修改后的值放回正确的位置。
这样做的最佳方式/方法是什么?
答案 0 :(得分:15)
递归访问所有嵌套值:
import collections
def map_nested_dicts(ob, func):
if isinstance(ob, collections.Mapping):
return {k: map_nested_dicts(v, func) for k, v in ob.iteritems()}
else:
return func(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}}
在某些情况下,需要修改原始的dict对象(以避免重新创建它):
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}}
如果您使用的是Python 3:
将import collections
替换为import collections.abc
将collections.Mapping
替换为collections.abc.Mapping
答案 1 :(得分:2)
只是为了扩展vaultah的答案,如果你的某个元素可以是一个列表,并且你也想要处理它们:
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)
答案 2 :(得分:0)
如果您需要它可用于任意嵌套中的列表和字典:
def apply_recursive(func, obj):
if isinstance(obj, dict): # if dict, apply to each key
return {k: apply_recursive(func, v) for k, v in obj.items()}
elif isinstance(obj, list): # if list, apply to each element
return [apply_recursive(func, elem) for elem in obj]
else:
return func(obj)
答案 3 :(得分:-1)
我有一个更通用的实现,可以接受任意数量的任意类型的容器作为参数。
from collections.abc import Iterable
import types
def dict_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}
def collection_map(fun, *collections):
assert len(collections) > 0
if isinstance(collections[0], dict):
return dict_value_map(fun, *collections)
if isinstance(collections[0], (tuple, list, set)):
return type(collections[0])(map(fun, *collections))
else:
return map(fun, *collections)
iscollection = lambda v:(isinstance(v,Iterable)and(not isinstance(v,str)))
def apply(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.
'''
if isinstance(at, int):
assert at >= 0
if at == 0:
return fun(*collections)
else:
return collection_map(lambda *cs:apply(fun, *cs, at=at-1), *collections)
if isinstance(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))
示例:
> 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')]
和
> 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}}