【问题标题】:Iterate Through All Nodes In yaml-cpp Including Recursive Anchor/Alias遍历 yaml-cpp 中的所有节点,包括递归锚点/别名
【发布时间】:2022-01-22 07:54:03
【问题描述】:

给定YAML::Node,我们如何访问该节点内的所有标量节点(以修改它们)?我最好的猜测是递归函数:

void parseNode(YAML::Node node) {
    if (node.IsMap()) {
        for (YAML::iterator it = node.begin(); it != node.end(); ++it) {
            parseNode(it->second);
        }
    }
    else if (node.IsSequence()) {
        for (YAML::iterator it = node.begin(); it != node.end(); ++it) {
            parseNode(*it);
        }
    }
    else if (node.IsScalar()) {
        // Perform modifications here.
    }
}

这可以满足我的需要,除了在一种情况下:如果有递归锚/别名。 (我认为 YAML 1.2 规范允许这样做?yaml-cpp 在解析时当然不会抛出 Exception)例如:

first: &firstanchor
  a: 5
  b: *firstanchor

上面的代码将重复跟随锚的别名,直到崩溃(SEGFAULT)可能是由于堆栈问题。如何克服这个问题?

  • 有没有更好的方法来遍历整个未知的 Node 结构来解决这个问题?
  • 有没有办法使用公共 API 从 Node 检查它是否是别名,这样我们就可以维护一堆以前访问过的别名来检测循环和中断。
  • 或者有没有办法获取一些唯一的Node 标识符,这样我就可以维护一堆以前访问过的节点?
  • 或者最后,这个递归锚点/别名是否不符合规范 - 在这种情况下,我如何检查它的出现以确保我可以返回适当的错误?

【问题讨论】:

    标签: c++ yaml yaml-cpp


    【解决方案1】:

    识别节点最明显的方法是使用它们的m_pNode 指针;但是,这是不可公开查询的。下一个最好的方法是使用bool Node::is(const Node& rhs) const,遗憾的是它不允许我们在搜索循环时进行任何排序以提高性能。

    由于您可能不仅想避免循环,而且还想避免两次下降到同一个子图中,因此您实际上需要记住所有访问过的节点。这意味着最终您将在您行走的子图中存储所有节点,并且空间复杂度为 O(n),并且由于我们可以使用的节点上没有顺序,因此时间复杂度为 O(n²),因为必须将每个节点与所有以前看到的节点。那是可怕的

    不管怎样,代码如下:

    class Visitor {
    public:
      using Callback = std::function<void(const YAML::Node&)>;
      Visitor(Callback cb): seen(), cb(cb) {}
    
      void operator()(const YAML::Node &cur) {
        seen.push_back(cur);
        if (cur.IsMap()) {
          for (const auto &pair : cur) {
            descend(pair.second);
          }
        } else if (node.IsSequence()) {
          for (const auto &child : cur) {
            descend(child);
          }
        } else if (node.IsScalar()) {
          cb(cur);
        }
      }
    private:
      void descend(const YAML::Node &target) {
        if (std::find(seen.begin(), seen.end(), target) != seen.end())
         (*this)(target);
      }
    
      std::vector<YAML::Node> seen;
      Callback cb;
    };
    

    (我们可以使用std::find,因为operator==在内部使用Node::is。)

    然后你就可以了

    Visitor([](const YAML::Node &scalar) {
      // do whatever with the scalar
    })(node);
    

    (请原谅我的错误,我的 C++ 有点生疏了;希望你明白。)

    【讨论】:

    • 不错!我忘记了Node::is。我会保留接受几天,看看是否有其他解决方案,但谢谢!
    • 我意识到我们可以做得比这更好。由于节点使用is 代替operator==,我们可以使用std::find(stack.begin(), stack.end(), target) 来搜索堆栈。
    • @SamBob 确实,我合并了这个。如果你想摆脱递归,你也可以有一堆std:pair&lt;YAML:Node, YAML:iterator&gt;;然后,您将有一个循环来推进顶部迭代器,推送它找到的任何新的未知节点,并在迭代器完成时弹出。我没有写那段代码,因为它离最初的问题太远了。
    • @SamBob 我已经意识到,如果您不仅想避免循环,而且还想避免两次访问任何节点,则不能弹出堆栈。我为此更新了答案,还将访问者 API 更改为使用 operator(),因为该对象应该在一行中创建和使用,而不是重复使用(因为新的步行需要一个新的 seen 列表)。
    猜你喜欢
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 2014-03-24
    • 1970-01-01
    相关资源
    最近更新 更多