一道快速考察Python基础的面试题

 一道快速考察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 中字典和列表对象都是可变对象,它们的变量传递给另一个变量后,改变对象元素会使得两个变量都会同时改变,比如:

new_data = {}  


tmp = {}  


new_data['a'] = tmp  


print(new_data)  # {'a': {}}  


tmp['b'] = 1  

print(new_data)  # {'a': {'b': 1}} 

如上,利用这个特性,将内层字典赋值给一个中间变量,然后改变这个中间变量,即可同步修改最终的 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 = {}  


for key, value in data.items():  


    keykeys = 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:  

            tmptmp = tmp[k] 

这也是群友给出的第一版答案,这样写并没有多大问题,但是代码比较繁琐,肯定还有优化空间。

我们可以只使用一个中间变量即可,进一步优化:

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  

        tmptmp = tmp[k]  # 将内层 dict 传给 tmp 

上面这个代码看似很简洁了,但是仍然还有两个 if 判断,如果不是使用了三元表达式的话,还会更多行。

所以可以进一步优化:

for field, value in data.items():   


    keys = field.split('_')   


    tmp = new_data   


    for k in keys[:-1]:   


        tmptmp = 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, {}) 

相关推荐