这是前一阵子群友发在群里的一道面试题,利用 Python 字典的特性,能够巧妙地使用精简代码达成完美解。python
将 data 转换成 new_data 这种形式,写出转换过程。面试
data = {
'a_b_h':1,
'a_b_i':2,
'a_c_j':3,
'a_d':4,
'a_c_k':5,
'a_e':6
}
new_data = {
'a':{
'b':{
'h':1,
'i':2
},
'c':{
'j':3,
'k':5
},
'd':4,
'e':6
}
}
复制代码
能够看出,转换的过程是将 key 的下划线进行拆分,而后下划线后边的字符嵌套在前面字符的值中。编程
感兴趣就打开 IDE,本身先试着解一下。微信
你应该很快想到,主要思路是将下划线 split
后,而后依次使用字符生成内层字典,当达到最后一个字符时将数字做为值。学习
那么关键点在于,如何不断地得到内层字典去修改呢?实际本题就是考察你是否理解 Python 字典是引用传递这个特性。优化
什么是引用传递?咱们知道 Python 中字典和列表对象都是可变对象,它们的变量传递给另外一个变量后,改变对象元素会使得两个变量都会同时改变,好比:spa
new_data = {}
tmp = {}
new_data['a'] = tmp
print(new_data) # {'a': {}}
tmp['b'] = 1
print(new_data) # {'a': {'b': 1}}
复制代码
如上,利用这个特性,将内层字典赋值给一个中间变量,而后改变这个中间变量,便可同步修改最终的 new_data 变量。插件
根据这个思路,初步代码以下:code
data = {
'a_b_h':1,
'a_b_i':2,
'a_c_j':3,
'a_d':4,
'a_c_k':5,
'a_e':6
}
new_data = {}
for key, value in data.items():
keys = key.split('_')
tmp = new_data
last = len(keys) - 1 # 最后一个 key 的索引值
for i, k in enumerate(keys):
if i == last:
tmp[k] = value
continue
if k not in tmp:
sub_tmp = {}
tmp[k] = sub_tmp
tmp = sub_tmp
else:
tmp = tmp[k]
复制代码
这也是群友给出的初版答案,这样写并无多大问题,可是代码比较繁琐,确定还有优化空间。cdn
咱们能够只使用一个中间变量便可,进一步优化:
for field, value in data.items():
keys = field.split('_')
tmp = new_data
last = len(keys) - 1
for i, k in enumerate(keys):
if k not in tmp:
tmp[k] = {} if i < last else value
tmp = tmp[k] # 将内层 dict 传给 tmp
复制代码
上面这个代码看似很简洁了,可是仍然还有两个 if 判断,若是不是使用了三元表达式的话,还会更多行。
因此能够进一步优化:
for field, value in data.items():
keys = field.split('_')
tmp = new_data
for k in keys[:-1]:
tmp = tmp.setdefault(k, {})
tmp[keys[-1]] = value
复制代码
咱们省略掉了 last 来判断最后一个字符的索引,直接经过 keys[:-1]
避开最后一个字符,末尾再单独生成数字键值对。
这里还使用字典的一个内置方法 —— setdefault
。
dict.setdefault(key, default=None)
方法和 get
方法相似,只是若是键不存在于字典中,不只会返回 default 参数的值,还同时会用该值自动生成一个键值对。
if k not in tmp:
tmp[k] = {}
v = tmp[k]
# 等价于
v = tmp.setdefault(k, {})
复制代码
最终咱们使用了 6 行代码就解出该题,这也是接近最简代码。
若是使用字典引用的特性是合格的话,那么当你用出 setdefault
这个方法后,面试官已经给你打了优秀,因此必定要熟悉这些数据对象的全部内置方法。
本文属于原创,首发于微信公众号「面向人生编程」,如需转载请后台留言。