遍历和提取树的节点时出现问题?
Posted
技术标签:
【中文标题】遍历和提取树的节点时出现问题?【英文标题】:Problems while traversing and extracting the nodes of a tree? 【发布时间】:2019-12-10 13:30:12 【问题描述】:我有以下树(tree_1、tree_2、tree_3)存储在字典(dict_1、dict_2、dict_3)中。 如何递归遍历树的所有路径,收集从根到每个分支的最后一个节点的所有节点?
换句话说,我想为树的所有分支(字典)生成所有可能的节点序列列表。例如,dict_1 (tree_1) 的一些可能分支是:
[["FunctionDef", "try", "ExceptHandler", "Expr", "Call", "Attribute","Load"],
["FunctionDef", "try", "ExceptHandler", "Expr", "Call", "Attribute","save_dictionary"],
["FunctionDef", "try", "ExceptHandler", "Expr", "Call", "Attribute","Name", "self"],
...,
["FunctionDef", "arguments", "arg", "self"]]
到目前为止,从上一个问题我尝试:
def foo(nested_dict, c = []):
for i in ['left', 'op', 'right', 'func', 'value', 'args', 'ctx',
'body', 'comparators', 'ops', 'test', 'orelse', 'targets', 'slice', 'n', 'id', '_type']:
if i in nested_dict:
if isinstance(nested_dict[i], list):
for b in nested_dict[i]:
yield from foo(b, c+[nested_dict['_type']])
elif isinstance(nested_dict[i], dict): #simple check here
yield from foo(nested_dict[i], c+[nested_dict['_type']])
else:
yield c+[nested_dict[i]]
和
def foo_2(nested_dict, c = []):
targets = 'left', 'op', 'right', 'func', 'value', 'args', 'ctx', 'body',
'comparators', 'ops', 'test', 'orelse', 'targets', 'slice', 'n',
'id', 'slice', 'annotation', 'arg', 'elts', 's', '_type'
for a, b in nested_dict.items():
if a in targets:
if isinstance(b, dict):
yield from foo_2(b, c+[a])
elif isinstance(b, list):
for i in b:
yield from foo_2(i, c+[a])
else:
yield c+[b]
但是,它们都不起作用,因为我得到了错误的序列。我试图修改目标,因为我认为这个问题与无法达到目标有关。尽管如此,我得到的路径不完整或不正确,而且通常它仍然无法正常工作,知道如何在给定一棵树的情况下为每个路径生成一个列表吗?
这是一个最小的例子,给定这棵树:
'_type': 'Expr',
'col_offset': 0,
'lineno': 1,
'value': '_type': 'Call',
'args': ['_type': 'BinOp',
'col_offset': 6,
'left': '_type': 'Num', 'col_offset': 6, 'lineno': 1, 'n': 1,
'lineno': 1,
'op': '_type': 'Add',
'right': '_type': 'Num', 'col_offset': 8, 'lineno': 1, 'n': 2],
'col_offset': 0,
'func': '_type': 'Name',
'col_offset': 0,
'ctx': '_type': 'Load',
'id': 'print',
'lineno': 1,
'keywords': [],
'lineno': 1
输出应该是:
[["Expr", "Call", "Name", "print"],
["Expr", "Call", "Name", "Load"],
["Expr", "Call", "Binop", "Num", "1"],
["Expr", "Call", "Binop", "add"],
["Expr", "Call", "Binop", "Num", "2"]]
【问题讨论】:
我添加了一个新的简短示例@kabanus 非常,很好。 指向tree_1
、tree_2
、tree_3
、dict_1
、dict_2
和dict_3
的链接都已失效。因此,SO 鼓励在您的帖子中托管代码示例和图片,而不是链接站外资源。
过期了,我再上传一次@user633183
【参考方案1】:
函数 1 的第一个小修复是注意以 dict
结尾的分支仅包含 _type
和以更简单对象结尾的分支(例如 id
),它们都由您的 else
处理,但是需要两个不同的东西:
-
第一个将通过
dict
递归调用处理,因此将添加前一个 _type
节点。
第二个,将在前一个节点迭代中到达else
,但只会添加自己,这意味着您忘记了“当前”_type
节点。所以else
变成:
elif i == '_type':
#Got here recursively, previous node already added
yield c+[nested_dict[i]]
else:
#Got here in the iteration of the "previous" node, so need to add it as well.
yield c+[nested_dict['_type'],nested_dict[i]]
不,您将获得所有分支,但您也会获得所有子分支
(['Expr'],['Expr','Call'],['Expr','Call','BinOp']...
)。这意味着我们在某个错误的地方让步了!我们现在正在产生_type
节点,即使它们不是叶子。同样清楚的是,无论如何我们总是需要c
才能拥有_type
。想到的第二个解决方案:
def foo(nested_dict, c = []):
yielded = False
c = c+[nested_dict['_type']] #Good place for try...except and validation check
for i in ['left', 'op', 'right', 'func', 'value', 'args', 'ctx',
'body', 'comparators', 'ops', 'test', 'orelse', 'targets', 'slice', 'n', 'id']:
if i in nested_dict:
yielded = True
if isinstance(nested_dict[i], list):
for b in nested_dict[i]:
yield from foo(b, c)
elif isinstance(nested_dict[i], dict): #simple check here
yield from foo(nested_dict[i], c)
else:
yield c+[nested_dict[i]]
#`_type` is leaf
if not yielded: yield c
请注意,我从操作中删除了_type
,因为现在迭代它没有意义。这样我们就可以在循环中使用else
。函数 2 也是一样的,所以我留给你修复它。
【讨论】:
运行您的大型示例,我看到您可能缺少操作/键(arg
,handlers
...)。如果你有一些规则,你如何得到这些会更健壮。
非常感谢您的帮助!尽管您建议的修复很好,但缺少遍历某些路径。例如,此tree 表示为此dict。输出为this,缺少一些路径。我可以为这些情况做些什么?我尝试将例如 name
添加到目标中,但它仍然无法正常工作
@JDo 就像我说的那样,您在 ['left','right'...]
的大键列表中缺少键,例如 arg
和 handlers
。添加它们,否则这些路径将被丢弃(由您自己设计)。
@JDo you'll have to be more specific - 是否仍然缺少相同的路径?是否只出现了一些,但仍然缺少一些?如果是后者,您可能只是想弄清楚列表中缺少哪些键。
@JDo 抱歉,您必须自己开始调试。我可以清楚地看到您在 x['body'][0]['_type']
(Expr
) 和 x['body'][0]['value']['_type']
(Str
) 链接到的 dict
中的“错误”路径 - 根据您迄今为止的定义,这是一条可接受的路径。您现在必须坐下来将代码修改为您想要的,而不是在这里添加要求 - 我们无法处理所有边缘情况。以上是关于遍历和提取树的节点时出现问题?的主要内容,如果未能解决你的问题,请参考以下文章
尝试打开 10.025 pdf 时出现“参数列表太长”[重复]