这是我关于Stackflow的第一个问题,所以如果我没有很好地格式化,我会提前道歉。我的问题如下: 我有以下嵌套字典/列表列表,我想要做的是创建一个名为" food"的新父类(L0)类别。并将水果和蔬菜词典中的所有价值向下移动一步(这样L0:果实变为L1:水果,L1:香蕉变成L2:香蕉)......
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"
}
]
}
]
}
]
现在,因为我的词典大小不一,有多深,我需要一个程序化的解决方案。所以我想我会创建一个反复迭代的函数,直到它到达树的末尾。当函数到达特定分支的末尾时,它将向密钥添加1(L0 - > L1,L1_list - > L2_list)。虽然这个过程确实将所有内容都降低了一级,但我无法弄清楚如何重建初始结构。特别是,我无法将孩子们带回各自的名单。
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的建议,我试图使用pop函数。但是,因为它发生在迭代中,它会弹出旧键,创建并插入新键,但是在下一次迭代中,将获取刚刚创建的新键,弹出它,并创建并插入新的新键,等等on(虽然它也没有进入无限循环)。 这是一个简化的例子来说明问题:
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]=[]
old_d = {'L0': 'Fruit', 'L1_list': [{'L1': 'Green apple'}, {'L1': 'Red apple'}]}
new_dict[lst_k].append(old_d)
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])
答案 0 :(得分:0)
您不应该创建新列表并将所有内容放入其中。实际上这就是你在做的事情:
Final_list.append(new_D)
您应该做的是递归迭代您拥有的所有词典和列表(与当前执行的相同),如果object是dict,则相应地重命名该dict中的所有键。
Here you can find how to rename keys i dict。我认为best answer there建议使用以下内容:
new_k = "L"+str(int(re.sub("L","",k))+1)
x[new_key] = x.pop(k)
最后,在完成挖掘所有D
之后,您应该将修改后的D
放入新Expected_output
列表中。
答案 1 :(得分: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__
不会给您带来任何问题。
由于在给定级别上您永远都不会在原始键和增量键之间得到重复,因此您不必特别注意丢失先前的值(例如,如果您有L1
和L2
在相同的字典中,并先增加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约定保持一致,我没有从就地函数返回任何内容。