這是前一陣子羣友發在羣裏的一道面試題,利用 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
這個方法後,面試官已經給你打了優秀,因此必定要熟悉這些數據對象的全部內置方法。
本文屬於原創,首發於微信公衆號「面向人生編程」,如需轉載請後臺留言。