我有一个任意嵌套的JSON对象(使用json.load解析),由dicts,lists,primitives等组成。我首先使用递归遍历深度并以linux fs格式跟踪节点的路径(对于列表,我将/ element附加到路径,因此在特定路径上可以有多个对象),如下所示:
def traverse(node, path =''):
if isinstance(node, dict):
for key in node:
traverse(node[key], path+'/'+key)
elif isinstance(node, list):
for elem in node:
traverse(elem,path+'/element')
每个节点可以包含一个字符串,需要使用可以存在于树中任何位置的对象的值来填充,在当前节点的相对路径中引用,例如:" {../../ key} {./child1/child2/key}"。
问题:我可以访问当前节点的子节点的值,但是我无法直接访问当前节点的父节点。
我认为的解决方案:我认为有一个解决方案是拥有一个元组列表(子节点,父节点)并将当前节点与我即将递归的子节点一起存储,然后搜索当我需要上去时反向列出。这有点危险,因为如果孩子是原始值,那么将等于具有相同值和类型的任何其他孩子,因此我可能检索错误的父母,但我认为反过来通过列表应该照顾那对吗?
我认为一个不同的解决方案是使用一个字典,其中key是子路径并且是父节点的值。我认为这应该更好,因为唯一的时间路径冲突是列表元素,但它们都有相同的父级所以我认为应该没问题。
还有其他建议吗?或者对这两种解决方案的任何评论? 谢谢
答案 0 :(得分:1)
回到dict的父级(或任何没有后向指针的递归结构)的唯一方法是在遍历时记住它。
但请注意,您已经这样做了:您的path
字符串是从顶部到当前节点的路径。
让我们编写一个使用路径的函数:
def follow(head, path):
if not path:
return head
first, _, rest = path.partition('/')
return follow(head[first], rest)
当然,建立path
作为键的元组而不是字符串可能更好,所以我们不必将它们分开(所以我们没有如果任何密钥可能包含/
等,请担心转义或引用;);你最后总是可以join
。
将path
构建为节点(或键 - 节点对)的元组而不仅仅是键可能更好,因此我们可以在常量时间内访问父节点{{1}而不是以path[-1]
为对数的时间。但这实际上取决于你实际上要做的事情;您的真实代码可能不会遍历树,构建到每个节点的路径,然后对它们不执行任何操作。
然而,解决这个问题的一个非常好的方法是将遍历内部转换为:make follow(head, path)
一个迭代器:
traverse
现在我们可以循环遍历def traverse(node, path =''):
if isinstance(node, dict):
for key in node:
yield from traverse(node[key], path+'/'+key)
elif isinstance(node, list):
for elem in node:
yield from traverse(elem,path+'/element')
yield (node, path)
以执行我们想要的任何操作作为后期深度优先遍历:
traverse
现在您可以轻松更改它以产生for node, path in traverse(root):
# do something
(无论node, parent, path
是父节点,还是父键,或者您想要的任何内容)。
答案 1 :(得分:0)
在Python中, vanilla 对象(至少那些用于JSON blob的对象,如list
,dict
等)有 no 关系收集和包含它们的对象。
这是有道理的,因为基本上列表可以多次包含相同的对象。此外,对象可以同时存储在字典和集合中。除非你执行某种垃圾收集算法(这在性能方面非常低效,并且将“扫描”覆盖所有对象),因此没有重建的重要方法引用给定对象的对象列表。即使我们要扫描这些对象,由于可能有多个父母,因此决定我们将看到的“父”仍然是微不足道的。
在Python中,字典甚至可以包含自身。像:
# example of constructing a datastructure containing itself
some_dict = {}
some_dict['a'] = some_dict
现在我们无休止地递归some_dict
,例如some_dict['a']['a']['a'] is some_dict
。
关键是,由于您以递归方式枚举,因此可以维护一个包含祖先的堆栈。例如:
def traverse(node, path ='', stack=None):
if stack is None:
stack = [node]
else:
stack.push(self)
if isinstance(node, dict):
for key in node:
traverse(node[key], path+'/'+key, stack)
elif isinstance(node, list):
for elem in node:
traverse(elem,path+'/element', stack)
stack.pop()
因此,在检查节点之前,每个节点都会在堆栈上推送自己,最后,它会从堆栈中弹出自己。我们以递归方式传递堆栈,因此每个递归调用都可以检查堆栈(不仅是父级,还有整个跟踪到根目录)。