遍历和提取​​树的节点时出现问题?

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_1tree_2tree_3dict_1dict_2dict_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 也是一样的,所以我留给你修复它。

【讨论】:

运行您的大型示例,我看到您可能缺少操作/键(arghandlers...)。如果你有一些规则,你如何得到这些会更健壮。 非常感谢您的帮助!尽管您建议的修复很好,但缺少遍历某些路径。例如,此tree 表示为此dict。输出为this,缺少一些路径。我可以为这些情况做些什么?我尝试将例如 name 添加到目标中,但它仍然无法正常工作 @JDo 就像我说的那样,您在 ['left','right'...] 的大键列表中缺少键,例如 arghandlers。添加它们,否则这些路径将被丢弃(由您自己设计)。 @JDo you'll have to be more specific - 是否仍然缺少相同的路径?是否只出现了一些,但仍然缺少一些?如果是后者,您可能只是想弄清楚列表中缺少哪些键。 @JDo 抱歉,您必须自己开始调试。我可以清楚地看到您在 x['body'][0]['_type'] (Expr) 和 x['body'][0]['value']['_type'] (Str) 链接到的 dict 中的“错误”路径 - 根据您迄今为止的定义,这是一条可接受的路径。您现在必须坐下来将代码修改为您想要的,而不是在这里添加要求 - 我们无法处理所有边缘情况。

以上是关于遍历和提取​​树的节点时出现问题?的主要内容,如果未能解决你的问题,请参考以下文章

Python二叉树的三种深度优先遍历

Python二叉树的三种深度优先遍历

尝试打开 10.025 pdf 时出现“参数列表太长”[重复]

尝试结合使用 jQuery 的 window.open 函数和 for 循环来遍历数组时出现问题

二叉树的遍历详解

尝试加入提取日期和月份时出现 SQL 错误