嵌套字典中的字典转换

5
我有一个嵌套字典和列表的列表。我想要创建一个新的父类(L0)叫做'food',并将fruitvegs中所有值向下移动一步(这样'L0': 'fruit'就变成了'L1': 'fruit''L1': 'banana'变成了'L2': 'banana',以此类推)。
D = [{
        "L0": "fruit",
        "L1_list": [
            {
                "L1": "banana"
            },
            {
                "L1": "apple", 
                "L2_list": [
                    {
                        "L2": "Green apple"
                    }, 
                    {
                        "L2": "Red apple"
                    }
                ]
            }, 
            {
                "L1": "kiwi"
            }
        ]
    },
    {
        "L0": "vegs", 
        "L1_list": [
            {
                "L1": "potato"
            }, 
            {
                "L1": "carrot"
            }
        ]
    }]

期望的输出应该是这样的:
Expected_output = [
    {
        "L0": "food",
        "L1_list": [
            {
                "L1": "fruit",
                "L2_list": [
                    {
                        "L2": "banana"
                    },
                    {
                        "L2": "apple",
                        "L3_list": [
                            {
                                "L3": "Green apple"
                            },
                            {
                                "L3": "Redapple"
                            }
                        ]
                    },
                    {
                        "L2": "kiwi"
                    }
                ]
            },
            {
                "L1": "vegs",
                "L2_list": [
                    {
                        "L2": "potato"
                    },
                    {
                        "L2": "carrot"
                    }
                ]
            }
        ]
    }
]

现在,由于我的字典大小和深度可能会不同,我需要一个程序化的解决方案。因此,我想创建一个递归函数,遍历直到达到树的末尾。当函数到达特定分支的末尾时,它将在键(L0 --> L1L1_list --> L2_list)中添加1。虽然这个过程确实将所有内容向下移动了一级,但我无法弄清如何重建初始结构。特别是,我无法将子项带回其各自的列表中。
Final_list = []
def digger(list_to_dig):
    import re
    for x in list_to_dig:
        for k,v in x.items():
            if isinstance(v, list):
                print("keep digging")
                digger(v)
            elif isinstance(x, dict):
                new_D = {}
                new_k = "L" + str(int(re.sub("L", "", k)) + 1)
                new_D[new_k] = v
                temp = re.sub("L", "", k)
                new_child_list = "L" + str(int(re.sub("_list", "", temp)) + 2) + "_list"
                new_D[new_child_list] = ""
                Final_list.append(new_D)
            else:
                print("no dictionary avail")
                pass
    print("_________")
    print(Final_list)
    print("_________")

    test = digger(D)

有关如何解决这个问题的建议吗?非常感谢。
根据@running.t的建议,我尝试使用dict.pop方法。然而,由于它发生在迭代中,它会弹出旧键,创建并插入新键,但在下一次迭代中将获取刚刚创建的新键,弹出它,并创建并插入一个新的新键,依此类推(虽然它也不会进入无限循环)。
以下是一个简化的示例,以说明问题:
第1步:创建新的顶级dict
new_top_level = {"L0": "Food"}
new_dict = {}
for k, v in new_top_level.items():
    lst_k = "L" + str(int(re.sub("L", "", ka)) + 1) + "_list"
    new_dict[k] = v
    new_dict[lst_k] = []

步骤2:将旧树添加到新列表中

old_d = {'L0': 'Fruit', 'L1_list': [{'L1': 'Green apple'}, {'L1': 'Red apple'}]}
new_dict[lst_k].append(old_d)

步骤三:将旧树的所有键值增加1

def digger(list_to_update):
    import re
    pattern1 = r"L.$"
    pattern2 = r"L._list"
    for x in list_to_update:
        for k1, v1 in x.items():
            if re.match(pattern1, k1):
                new_k1 = "L" + str(int(re.sub("L", "", k1)) + 1)
                x[new_k1] = x.pop(k1)
            elif re.match(pattern2, k1):
                temp = re.sub("L", "", k1)
                new_k1 = "L" + str(int(re.sub("_list", "", temp)) + 1) + "_list"
                x[new_k1] = x.pop(k1)
                digger(v1)

test = digger(new_dict[lst_k])

你从哪里获取这个初始字典?是自己构建的吗? - Igl3
是的,我手动构建了那个特定的字典。不过,在最终解决方案中,它将由最终用户通过用户界面构建。 - Intel_code
L10是什么情况? - Mad Physicist
2个回答

0

你不应该创建新的列表并把所有东西放进去。实际上,这就是你在这一行中所做的事情:

Final_list.append(new_D)

你应该做的是递归迭代你拥有的所有字典和列表(与你当前所做的方式相同),如果对象是一个字典,则相应地重命名该字典中的所有键。

在这里你可以找到如何重命名字典键。我认为最佳答案建议使用以下方法:

new_k = "L"+str(int(re.sub("L","",k))+1) 
x[new_key] = x.pop(k)

最后,完成挖掘所有D之后,您应将修改后的D放入新的Expected_output列表中。

谢谢!非常有用。我不熟悉pop函数,但会研究一下并告诉你结果。 - Intel_code

0
一年晚了,我知道,但让我们简要分析一下这个问题。你有一个字典。字典可以有两种类型的键:L*L*_list。在这两种情况下,*都是整数。L*将始终具有字符串值。L*_list将始终具有列表-字典值。您的目标是递归地增加键名中的整数。
显然,这样的东西很适合递归。您会递归到L*_list值的每个元素中。当您获得不具有L*_list键的字典列表时,递归结束。在这种情况下,您只需要增加L*键并返回即可。到目前为止,我们完全同意,因为我所说的一切都已经在问题中了。
为了回答实际问题,我们只需要进行一次更改:递归函数需要修改嵌套对象本身,或者返回一个新的替换对象。构造一个全新的数据结构比直接修改现有字典更简单,因为它使迭代更容易(您也注意到了这一点)。

在顶层有一个特殊情况,因为你想将所有东西都推进一个新的food类别中。这不是问题,因为递归解决方案将返回新L1_list键的值。

这里是一个简单的示例实现:

def increment_keys(d):
    def process_key(key, value):
        key = f'L{int(key[1:]) + 1}'
        return key, value

    def process_list(key, value):
        key = f'L{int(key[1:-5]) + 1}_list'
        value = [increment_keys(d) for d in value]
        return key, value

    def process(key, value):
        if key.endswith('_list'):
            return process_list(key, value)
        return process_key(key, value)

    return dict(process(key, value) for key, value in d.items())

expected_output = [{'L0': 'food', **increment_keys({'L0_list': D})}]

您可以使用三元运算符将嵌套的process函数吸收到馈入increment_keys返回值的生成器中。我认为这并没有提高可读性,但可以节省大约四行代码:

return dict(process_list(k, v) if k.endswith('_list') else process_key(k, v) for k, v in d)

如果你非常需要在原地进行这个操作,最好的方法是在迭代之前冻结每个字典的键。如果你在冻结的键上迭代,pop__setitem__ 将不会引起任何问题。

由于在给定级别上,原始键和增量键之间永远不会出现重复,因此您不必特别注意丢失先前的值(例如,如果您在同一个字典中有 L1L2,并首先增加了 L1)。

以下是一个示例就地递归:

def increment_keys(obj):
    def process(d):
        for key in list(d.keys()):
            value = d.pop(key)
            if key.endswith('_list'):
                key = f'L{int(key[1:-5]) + 1}_list'
                increment_keys(value)
            else:
                key = f'L{int(key[1:]) + 1}'
            d[key] = value

    for d in obj:
        process(d)

increment_keys(D)
expected_output = [{'L0': 'food', 'L1_list': D}]

根据Python的惯例,我没有从原地函数中返回任何内容。

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