A T*_*mes 147 python dictionary
假设你有一个字典,如:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Run Code Online (Sandbox Code Playgroud)
你会如何将其扁平化为:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Run Code Online (Sandbox Code Playgroud)
Imr*_*ran 193
基本上与平展嵌套列表的方式相同,您只需要执行额外的工作来按键/值迭代dict,为新字典创建新键并在最后一步创建字典.
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}
Run Code Online (Sandbox Code Playgroud)
nin*_*cko 59
原始海报需要考虑两个重要因素:
{'a_b':{'c':1}, 'a':{'b_c':2}}会导致{'a_b_c':???}.以下解决方案通过返回可迭代的对来避免问题.joinedKey = '_'.join(*keys),那将花费你O(N ^ 2)的运行时间.但是,如果你愿意说nextKey = previousKey+'_'+thisKey,这会让你获得O(N)时间.下面的解决方案允许您同时执行这两个操作(因为您只能连接所有键,然后对它们进行后处理).(性能不太可能是一个问题,但我会详细说明其他人关心的第二点:在实现这一点时,有许多危险的选择.如果你递归地执行此操作并且产生并重新产生,或者任何等效的接触节点超过一次(这是很容易不小心做),你正在做的潜在O(N ^ 2)工作,而不是O(N).这是因为也许你正在计算的关键a,然后a_1再a_1_i...,然后计算a再a_1然后a_1_ii......,但真的是你不应该计算a_1一次.即使你不重新计算它,重新得到它("级别由级"方法)是一样糟糕.一个很好的例子是考虑性能{1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}})
下面是我写的一个功能flattenDict(d, join=..., lift=...),它可以适应多种用途,可以做你想要的.遗憾的是,在不产生上述性能损失的情况下制作这个函数的懒惰版本相当困难(许多python内置函数如chain.from_iterable实际上并不高效,我只是在对此代码的三个不同版本进行大量测试之后才意识到这一点.这个).
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
Run Code Online (Sandbox Code Playgroud)
为了更好地理解正在发生的事情,下面是不熟悉reduce(左)的人的图表,也称为"向左折叠".有时用初始值代替k0绘制(不是列表的一部分,传递给函数).在这里,J是我们的join功能.我们每次进行预处理ķ ñ用lift(k).
[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
Run Code Online (Sandbox Code Playgroud)
这实际上是相同的functools.reduce,但是我们的函数对树的所有关键路径执行此操作.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Run Code Online (Sandbox Code Playgroud)
演示(我以其他方式放入docstring):
>>> 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}
Run Code Online (Sandbox Code Playgroud)
性能:
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
Run Code Online (Sandbox Code Playgroud)
......感叹,不要以为那是我的错......
[由于节制问题导致的不重要的历史记录]
关于Flatten的一个字典词典(2级深度)在Python中被称为复制:
这个问题的解决方案可以通过这样做来实现sorted( sum(flatten(...),[]) ).反向是不可能的:虽然这是事实,所述值的flatten(...)可以从重复的指称通过映射高阶累加器被回收,一个不能恢复键.(编辑:事实证明,所谓的重复所有者的问题是完全不同的,因为它只处理2级深度的字典,尽管该页面上的一个答案给出了一般解决方案.)
MYG*_*YGz 37
或者,如果你已经在使用熊猫,你可以这样做json_normalize():
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])
Run Code Online (Sandbox Code Playgroud)
输出:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Run Code Online (Sandbox Code Playgroud)
div*_*ero 26
这是一种"功能","一线"的实现.它是递归的,基于条件表达式和字典理解.
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 }
Run Code Online (Sandbox Code Playgroud)
测试:
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}
Run Code Online (Sandbox Code Playgroud)
Aar*_*ock 19
如果您正在使用pandas,则在pandas.io.json._normalize被调用nested_to_record中隐藏了一个功能,它可以完全执行此操作.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
Run Code Online (Sandbox Code Playgroud)
Nik*_* VJ 13
不完全是 OP 所要求的,但是很多人来到这里寻找方法来扁平化现实世界的嵌套 JSON 数据,这些数据可以在数组中嵌套键值 json 对象和数组以及 json 对象等等。JSON 不包含元组,因此我们不必担心这些。
我找到了@roneo对@Imran 发布的答案的列表包含评论的实现:
https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8
import collections
def flatten(dictionary, parent_key=False, separator='.'):
"""
Turn a nested dictionary into a flattened dictionary
:param dictionary: The dictionary to flatten
:param parent_key: The string to prepend to dictionary's keys
:param separator: The string used to separate flattened keys
:return: A flattened dictionary
"""
items = []
for key, value in dictionary.items():
new_key = str(parent_key) + separator + key if parent_key else key
if isinstance(value, collections.MutableMapping):
items.extend(flatten(value, new_key, separator).items())
elif isinstance(value, list):
for k, v in enumerate(value):
items.extend(flatten({str(k): v}, new_key).items())
else:
items.append((new_key, value))
return dict(items)
Run Code Online (Sandbox Code Playgroud)
测试一下:
flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3] })
>> {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3}
Run Code Online (Sandbox Code Playgroud)
这完成了我需要完成的工作:我把任何复杂的 json 扔在这个上面,它为我把它弄平了。
所有学分都归于 https://github.com/ScriptSmith。
Pav*_*ili 12
码:
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,''))
Run Code Online (Sandbox Code Playgroud)
结果:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Run Code Online (Sandbox Code Playgroud)
我正在使用python3.2,更新您的python版本.
如果您是pythonic oneliners的粉丝:
my_dict={'a': 1,'c': {'a': 2,'b': {'x': 5,'y' : 10}},'d': [1, 2, 3]}
list(pd.json_normalize(my_dict).T.to_dict().values())[0]
Run Code Online (Sandbox Code Playgroud)
返回:
{'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd': [1, 2, 3]}
Run Code Online (Sandbox Code Playgroud)
[0]如果您有一个字典列表而不仅仅是一个字典,则可以从末尾开始。
这不仅限于字典,而是每种映射类型.因为它避免了if条件,所以更快.然而,归功于伊姆兰:
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)
Run Code Online (Sandbox Code Playgroud)
Python3.5中的功能和高性能解决方案怎么样?
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(),
{}
))
Run Code Online (Sandbox Code Playgroud)
这更加高效:
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(),
{}
))
Run Code Online (Sandbox Code Playgroud)
正在使用:
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}
Run Code Online (Sandbox Code Playgroud)
利用递归,保持简单和人类可读:
def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
if accumulator is None:
accumulator = {}
for k, v in dictionary.items():
k = f"{parent_key}{separator}{k}" if parent_key else k
if isinstance(v, dict):
flatten_dict(dictionary=v, accumulator=accumulator, parent_key=k)
continue
accumulator[k] = v
return accumulator
Run Code Online (Sandbox Code Playgroud)
调用很简单:
new_dict = flatten_dict(dictionary)
Run Code Online (Sandbox Code Playgroud)
或者
new_dict = flatten_dict(dictionary, separator="_")
Run Code Online (Sandbox Code Playgroud)
如果我们想更改默认分隔符。
一点分解:
当函数第一次被调用时,它被调用只传递dictionary我们想要展平的。该accumulator参数是在这里支持递归,我们将在后面看到。因此,我们实例化为accumulator一个空字典,我们将在其中放置原始dictionary.
if accumulator is None:
accumulator = {}
Run Code Online (Sandbox Code Playgroud)
当我们迭代字典的值时,我们为每个值构造一个键。该parent_key参数将是None第一个呼叫,而对于每一个嵌套的字典,它会包含指向它的关键,所以我们前置一个关键。
k = f"{parent_key}{separator}{k}" if parent_key else k
Run Code Online (Sandbox Code Playgroud)
如果v键k指向的值是字典,则函数调用自身,传递嵌套字典accumulator(通过引用传递,因此对它所做的所有更改都在同一个实例上完成)和键,k以便我们可以构造连接键。注意continue声明。我们想跳过if块外的下一行,这样嵌套字典就不会出现在accumulatorunder key 中k。
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Run Code Online (Sandbox Code Playgroud)
那么,如果值v不是字典,我们该怎么办?只需将其放在accumulator.
accumulator[k] = v
Run Code Online (Sandbox Code Playgroud)
完成后,我们只需返回accumulator,保持原始dictionary参数不变。
笔记
这仅适用于以字符串作为键的字典。它将与实现该__repr__方法的可散列对象一起使用,但会产生不需要的结果。
小智 5
我的Python 3.3解决方案使用生成器:
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}
Run Code Online (Sandbox Code Playgroud)
这是使用堆栈的解决方案。没有递归。
def flatten_nested_dict(nested):
stack = list(nested.items())
ans = {}
while stack:
key, val = stack.pop()
if isinstance(val, dict):
for sub_key, sub_val in val.items():
stack.append((f"{key}_{sub_key}", sub_val))
else:
ans[key] = val
return ans
Run Code Online (Sandbox Code Playgroud)