Flatten nested dictionaries, compressing keys
假设你有一本像这样的字典:
1 2 3 4 5 | {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]} |
你将如何把它展平成:
1 2 3 4 5 | {'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]} |
号
基本上,与扁平嵌套列表的方法相同,您只需要做额外的工作,按键/值迭代dict,为新字典创建新键,并在最后一步创建字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import collections def flatten(d, parent_key='', sep='_'): items = [] for k, v in d.items(): new_key = parent_key + sep + k if parent_key else k if isinstance(v, collections.MutableMapping): items.extend(flatten(v, new_key, sep=sep).items()) else: items.append((new_key, v)) return dict(items) >>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}) {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10} |
原始海报需要考虑两个重要因素:
(性能不太可能是一个问题,但我将详细阐述第二点,以防其他人关心:在实现这一点时,有许多危险的选择。如果您递归地执行此操作并生成和重新生成,或者任何与节点接触多次的等效操作(这很容易意外地执行),那么您可能正在执行O(n^2)工作,而不是O(n)。这是因为你可能正在计算一个键
下面是我写的一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from collections import Mapping from itertools import chain from operator import add _FLAG_FIRST = object() def flattenDict(d, join=add, lift=lambda x:x): results = [] def visit(subdict, results, partialKey): for k,v in subdict.items(): newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k)) if isinstance(v,Mapping): visit(v, results, newKey) else: results.append((newKey,v)) visit(d, results, _FLAG_FIRST) return results |
为了更好地理解正在发生的事情,下面是不熟悉EDOCX1(左边)的人的图表,也就是所谓的"左折叠"。有时它是用初始值代替k0(不是列表的一部分,传递到函数中)绘制的。这里,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [k0,k1,...,kN].foldleft(J) / \ ... kN / J(k0,J(k1,J(k2,k3))) / \ / \ J(J(k0,k1),k2) k3 / \ / \ J(k0,k1) k2 / \ / \ k0 k1 |
。
这实际上与
1 2 | >>> reduce(lambda a,b:(a,b), range(5)) ((((0, 1), 2), 3), 4) |
。
演示(我把它放在docstring中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | >>> testData = { 'a':1, 'b':2, 'c':{ 'aa':11, 'bb':22, 'cc':{ 'aaa':111 } } } from pprint import pprint as pp >>> pp(dict( flattenDict(testData, lift=lambda x:(x,)) )) {('a',): 1, ('b',): 2, ('c', 'aa'): 11, ('c', 'bb'): 22, ('c', 'cc', 'aaa'): 111} >>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b) )) {'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111} >>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) )) {1: 12416037344, 2: 12544037731, 11: 5470935132935744593, 22: 4885734186131977315, 111: 3461911260025554326} |
性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | from functools import reduce def makeEvilDict(n): return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n)) import timeit def time(runnable): t0 = timeit.default_timer() _ = runnable() t1 = timeit.default_timer() print('took {:.2f} seconds'.format(t1-t0)) >>> pp(makeEvilDict(8)) {7: {6: {5: {4: {3: {2: {1: {0: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0}}}}}}}}} import sys sys.setrecursionlimit(1000000) forget = lambda a,b:'' >>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) ) took 0.10 seconds >>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) ) [1] 12569 segmentation fault python |
。
…叹息,别以为那是我的错…
[由于适度问题,不重要的历史注释]
关于所谓的python中的flatten a dictionary of dictionary(2 levels deep)of list的副本:
这个问题的解决方案可以通过执行
或者,如果您已经在使用熊猫,您可以使用
1 2 3 4 5 6 7 8 9 | import pandas as pd d = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]} df = pd.io.json.json_normalize(d, sep='_') print(df.to_dict(orient='records')[0]) |
。
输出:
1 | {'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]} |
这里是一种"功能性的"、"一行程序"实现。它是递归的,基于条件表达式和听写理解。
1 2 3 4 5 | def flatten_dict(dd, separator='_', prefix=''): return { prefix + separator + k if prefix else k : v for kk, vv in dd.items() for k, v in flatten_dict(vv, separator, kk).items() } if isinstance(dd, dict) else { prefix : dd } |
测试:
1 2 3 4 5 6 7 8 | In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231},"43234":1321}}, '.') Out[2]: {'abc': 123, 'gfd': 902, 'hgf.gh': 432, 'hgf.yu': 433, 'xzxzxz.432.0b0b0b': 231, 'xzxzxz.43234': 1321} |
号
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 | test = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]} def parse_dict(init, lkey=''): ret = {} for rkey,val in init.items(): key = lkey+rkey if isinstance(val, dict): ret.update(parse_dict(val, key+'_')) else: ret[key] = val return ret print(parse_dict(test,'')) |
号
结果:
1 2 | $ python test.py {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10} |
我正在使用python3.2,更新您的python版本。
如果您使用的是
1 2 3 | from pandas.io.json.normalize import nested_to_record flat = nested_to_record(my_dict, sep='_') |
号
这不限于字典,而是实现.items()的每个映射类型。更进一步是更快,因为它避免了一个if条件。然而,学分归伊姆兰:
1 2 3 4 5 6 7 8 | def flatten(d, parent_key=''): items = [] for k, v in d.items(): try: items.extend(flatten(v, '%s%s_' % (parent_key, k)).items()) except AttributeError: items.append(('%s%s' % (parent_key, k), v)) return dict(items) |
python3.5中的功能性和性能性解决方案如何?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from functools import reduce def _reducer(items, key, val, pref): if isinstance(val, dict): return {**items, **flatten(val, pref + key)} else: return {**items, pref + key: val} def flatten(d, pref=''): return(reduce( lambda new_d, kv: _reducer(new_d, *kv, pref), d.items(), {} )) |
。
这甚至更具表现力:
1 2 3 4 5 6 7 8 9 | def flatten(d, pref=''): return(reduce( lambda new_d, kv: \ isinstance(kv[1], dict) and \ {**new_d, **flatten(kv[1], pref + kv[0])} or \ {**new_d, pref + kv[0]: kv[1]}, d.items(), {} )) |
使用中:
1 2 3 4 | my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]} print(flatten(my_obj)) # {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1} |
。
使用生成器的python 3.3解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def flattenit(pyobj, keystring=''): if type(pyobj) is dict: if (type(pyobj) is dict): keystring = keystring +"_" if keystring else keystring for k in pyobj: yield from flattenit(pyobj[k], keystring + k) elif (type(pyobj) is list): for lelm in pyobj: yield from flatten(lelm, keystring) else: yield keystring, pyobj my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]} #your flattened dictionary object flattened={k:v for k,v in flattenit(my_obj)} print(flattened) # result: {'c_b_y': 10, 'd': [1, 2, 3], 'c_a': 2, 'a': 1, 'c_b_x': 5} |
号
扁平嵌套字典的简单函数。对于python 3,用
1 2 3 4 5 6 7 8 9 10 11 12 | def flatten_dict(init_dict): res_dict = {} if type(init_dict) is not dict: return res_dict for k, v in init_dict.iteritems(): if type(v) == dict: res_dict.update(flatten_dict(v)) else: res_dict[k] = v return res_dict |
号
想法/要求是:获取不保留父键的平面字典。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 | dd = {'a': 3, 'b': {'c': 4, 'd': 5}, 'e': {'f': {'g': 1, 'h': 2} }, 'i': 9, } flatten_dict(dd) >> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9} |
号
保存父键也很简单。
这与伊姆兰和罗卢的回答相似。它不使用生成器,而是使用带有闭包的递归:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def flatten_dict(d, separator='_'): final = {} def _flatten_dict(obj, parent_keys=[]): for k, v in obj.iteritems(): if isinstance(v, dict): _flatten_dict(v, parent_keys + [k]) else: key = separator.join(parent_keys + [k]) final[key] = v _flatten_dict(d) return final >>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}) {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10} |
。
当嵌套dict还包含dict列表时,davoud的解决方案非常好,但没有给出令人满意的结果,但是他的代码适用于这种情况:
1 2 3 4 5 6 7 8 9 10 11 | def flatten_dict(d): items = [] for k, v in d.items(): try: if (type(v)==type([])): for l in v: items.extend(flatten_dict(l).items()) else: items.extend(flatten_dict(v).items()) except AttributeError: items.append((k, v)) return dict(items) |
这是一个优雅的就地替换算法。用python 2.7和python 3.5测试。使用点字符作为分隔符。
1 2 3 4 5 6 7 8 | def flatten_json(json): if type(json) == dict: for k, v in list(json.items()): if type(v) == dict: flatten_json(v) json.pop(k) for k2, v2 in v.items(): json[k+"."+k2] = v2 |
。
例子:
1 2 3 4 5 | d = {'a': {'b': 'c'}} flatten_json(d) print(d) unflatten_json(d) print(d) |
输出:
1 2 | {'a.b': 'c'} {'a': {'b': 'c'}} |
。
我在这里发布了这段代码以及匹配的
上面的答案非常有效。我只是想加上我写的unfratten函数:
1 2 3 4 5 6 7 8 9 10 | def unflatten(d): ud = {} for k, v in d.items(): context = ud for sub_key in k.split('_')[:-1]: if sub_key not in context: context[sub_key] = {} context = context[sub_key] context[k.split('_')[-1]] = v return ud |
号
注意:这并不能解释已经存在于键中的"uu",就像扁平的对应键一样。
如果您想要平面嵌套字典和所有唯一键列表,那么下面是解决方案:
1 2 3 4 5 6 7 | def flat_dict_return_unique_key(data, unique_keys=set()): if isinstance(data, dict): [unique_keys.add(i) for i in data.keys()] for each_v in data.values(): if isinstance(each_v, dict): flat_dict_return_unique_key(each_v, unique_keys) return list(set(unique_keys)) |
号
在简单的嵌套列表(如递归)中使用dict.popItem():
1 2 3 4 5 6 7 8 9 10 11 12 13 | def flatten(d): if d == {}: return d else: k,v = d.popitem() if (dict != type(v)): return {k:v, **flatten(d)} else: flat_kv = flatten(v) for k1 in list(flat_kv.keys()): flat_kv[k + '_' + k1] = flat_kv[k1] del flat_kv[k1] return {**flat_kv, **flatten(d)} |
号
使用发电机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def flat_dic_helper(prepand,d): if len(prepand) > 0: prepand = prepand +"_" for k in d: i=d[k] if type(i).__name__=='dict': r = flat_dic_helper(prepand+k,i) for j in r: yield j else: yield (prepand+k,i) def flat_dic(d): return dict(flat_dic_helper("",d)) d={'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]} print(flat_dic(d)) >> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10} |
。
1 2 3 4 5 6 7 8 9 10 11 12 | def flatten(unflattened_dict, separator='_'): flattened_dict = {} for k, v in unflattened_dict.items(): if isinstance(v, dict): sub_flattened_dict = flatten(v, separator) for k2, v2 in sub_flattened_dict.items(): flattened_dict[k + separator + k2] = v2 else: flattened_dict[k] = v return flattened_dict |
号
我总是喜欢通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def flat_items(d, key_separator='.'): """ Flattens the dictionary containing other dictionaries like here: https://stackoverflow.com/questions/6027558/flatten-nested-python-dictionaries-compressing-keys >>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]} >>> flat = dict(flat_items(example, key_separator='_')) >>> assert flat['c_b_y'] == 10 """ for k, v in d.items(): if type(v) is dict: for k1, v1 in flat_items(v, key_separator=key_separator): yield key_separator.join((k, k1)), v1 else: yield k, v |
号