Python 3:将嵌套字典和包含在字典中的列表展开

10

我正在处理一个复杂的嵌套字典和列表数据结构。 我需要将数据展平,并将所有嵌套项带到级别0。 请参见下面的示例以获取更多明确信息:

{a:1,b:2,c:{c1:[{c11:1,c12:2,c13:3},{c21:1,c22:2,c23:3}],d1:[{d11:1,d12:2,d13:3},{d21:1,d22:2,d23:3}]},x:1,y:2}

我需要将这个扁平化为:

{a:1,b:2,c_c1_c11:1, c_c1_c12:2,c_c1_c13:3,c_c1_c21:1,c_c1_c22:2,c_c1_c23:3, c_d1,d11:1...and so on}

我参考了这篇文章中的第一个答案,但它只适用于嵌套字典,而不是列表嵌套在字典中,且更多的字典嵌套在这些列表中。

我稍微修改了代码以适应我的用例,但该代码无法正常工作。

def flattenDict(d):
node_map = {}
node_path = []
def nodeRecursiveMap(d, node_path):
    for key, val in d.items():
        if ((type(val) is not dict)&(type(val) is not list)): 
            node_map['_'.join(node_path + [key])] = val
        if type(val) is list:
            def nodeListRecursion(val,node_path):
                for element in val:
                    if ((type(element) is not dict)&(type(element) is not list)) : node_map['_'.join(node_path + [key])] = element
                    if type(element) is list: nodeListRecursion(element,node_map)
                    if type(element) is dict: nodeRecursiveMap(element, node_path + [key])
            nodeListRecursion(val,node_path)
        if type(val) is dict: nodeRecursiveMap(val, node_path + [key])
nodeRecursiveMap(d, node_path)
return node_map

当我在这里粘贴代码时,缩进混乱了。但我真的很感谢任何帮助。

3个回答

21

我认为您正在过于复杂化事情。您从一个词典开始,其中有键和值。其值是要向下递归的字典或字典列表,或者它们不是,这种情况下您要保持原样。所以:

def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for subdict in val:
                deeper = flatten(subdict).items()
                out.update({key + '_' + key2: val2 for key2, val2 in deeper})
        else:
            out[key] = val
    return out

给了我

In [34]: nested = {'a': 1, 'b': 2, 'c': {'c1': [{'c11': 1, 'c12': 2, 'c13': 3}, {'c21': 1, 'c22': 2, 'c23': 3}], 'd1': [{'d11': 1, 'd12': 2, 'd13': 3}, {'d21': 1, 'd22': 2, 'd23': 3}]}, 'x': 1, 'y': 2}

In [35]: flatten(nested)
Out[35]: 
{'a': 1,
 'b': 2,
 'c_c1_c11': 1,
 'c_c1_c12': 2,
 'c_c1_c13': 3,
 'c_c1_c21': 1,
 'c_c1_c22': 2,
 'c_c1_c23': 3,
 'c_d1_d11': 1,
 'c_d1_d12': 2,
 'c_d1_d13': 3,
 'c_d1_d21': 1,
 'c_d1_d22': 2,
 'c_d1_d23': 3,
 'x': 1,
 'y': 2}

1
点赞。使用 val = [val] 来处理字典值和列表值的方式非常聪明。 - blhsing
谢谢,这个可行。我刚意识到,在我的实际数据中,键在列表的不同子字典中重复出现。因此,最终展平的输出只有最后一个子字典(覆盖了它上面的那些)。 - Ashish Kohli
不是一个好的解决方案。例如对于嵌套 = {'asd': [{'a': 'hi'}, {'a': 'hi2'}]},您将丢失值'hi'。并且对于非字典列表(例如:nested = {'asd' : ['a', 'b' ] })会导致崩溃。 - Daniel Braun
@DanielBraun:你说得对,对于与OP不同形状的输入,你需要不同的解决方案。这是...意料之外的。 - DSM
@DSM 我认为唯一字段命名是操作方式传达思想的方式,而不是输入属性的已知属性。 - Daniel Braun
这很棒 - 我一直在尝试测试嵌套的 dotmap.DotMap/OrderedDict/dict 以用于自定义/非内置类型 - 而不是遍历它,我首先将其转换为一个扁平化对象,然后(现在很容易)循环遍历值 - 谢谢。 - jtlz2

15
在我的项目中,我使用了来自DSMs answer更新版本的函数来展开可能包含其他dict或list或dict列表的dict。我希望这对你有所帮助。
def flatten(input_dict, separator='_', prefix=''):
    output_dict = {}
    for key, value in input_dict.items():
        if isinstance(value, dict) and value:
            deeper = flatten(value, separator, prefix+key+separator)
            output_dict.update({key2: val2 for key2, val2 in deeper.items()})
        elif isinstance(value, list) and value:
            for index, sublist in enumerate(value, start=1):
                if isinstance(sublist, dict) and sublist:
                    deeper = flatten(sublist, separator, prefix+key+separator+str(index)+separator)
                    output_dict.update({key2: val2 for key2, val2 in deeper.items()})
                else:
                    output_dict[prefix+key+separator+str(index)] = value
        else:
            output_dict[prefix+key] = value
    return output_dict

0
更新DSMs answer以支持具有相同键的字典列表,通过将索引与键连接起来,而不会给代码增加太多复杂性。 代码:
def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for subidx, subdict in enumerate(val):
                deeper = flatten(subdict).items()
                out.update({key + f'_{subidx}' + '_' + key2 + f'_{idx}': val2 
                            for idx, (key2, val2) in enumerate(deeper)})
        else:
            out[key] = val
    return out

输入:

nested = {'a': 1, 
          'b': 2, 
          'c': {'c1': [{'c11': 1, 'c12': 2, 'c13': 3}, {'c21': 1, 'c22': 2, 'c23': 3}], 
                'd1': [{'dd1': 1, 'dd2': 2}, {'dd1': 3, 'dd2': 4}]}, # same keys
          'x': [{'xx1': 1, 'xx2': 2}, {'xx1': 3, 'xx2': 4}], # same keys
          'y': 2}

输出:

{'a': 1,
 'b': 2,
 'c_0_c1_0_c11_0_0': 1,
 'c_0_c1_0_c12_1_1': 2,
 'c_0_c1_0_c13_2_2': 3,
 'c_0_c1_1_c21_0_3': 1,
 'c_0_c1_1_c22_1_4': 2,
 'c_0_c1_1_c23_2_5': 3,
 'c_0_d1_0_dd1_0_6': 1,
 'c_0_d1_0_dd2_1_7': 2,
 'c_0_d1_1_dd1_0_8': 3,
 'c_0_d1_1_dd2_1_9': 4,
 'x_0_xx1_0': 1,
 'x_0_xx2_1': 2,
 'x_1_xx1_0': 3,
 'x_1_xx2_1': 4,
 'y': 2}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接