遍历 yaml-cpp 中的所有节点,包括递归 Anchor/Alias
Iterate Through All Nodes In yaml-cpp Including Recursive Anchor/Alias
给定一个 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.
}
}
除了在一种情况下,这可以很好地满足我的需要:如果有递归 anchor/alias。 (我认为这是 YAML 1.2 规范允许的?yaml-cpp
在解析时肯定不会抛出 Exception
)例如:
first: &firstanchor
a: 5
b: *firstanchor
上面的代码将重复跟随别名到锚点,直到崩溃(SEGFAULT)可能是由于堆栈问题。如何克服这一问题?
- 是否有更好的方法遍历整个未知的
Node
结构来解决这个问题?
- 有没有办法从
Node
和 public API 检查它是否是一个别名,这样我们就可以维护一个以前访问过的别名的堆栈来检测循环和中断.
- 或者有没有办法获取一些唯一的
Node
标识符,以便我可以维护以前访问过的节点的堆栈?
- 或者最后这个递归 anchor/alias 只是不符合规范 - 在这种情况下,我如何检查它的出现以确保我可以 return 一个适当的错误?
识别节点最明显的方法是使用它们的 m_pNode
指针;然而,这是不可公开查询的。下一个最佳方法是使用 bool Node::is(const Node& rhs) const
,遗憾的是,它不允许我们在搜索循环时进行任何排序以加快性能。
由于您可能不仅想避免循环,而且还想避免两次下降到同一个子图中,因此您实际上需要记住所有访问过的节点。这意味着最终您会将所有节点存储在您遍历的子图中,并且具有 space 复杂度 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++ 有点生疏;我希望你能理解。)
给定一个 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.
}
}
除了在一种情况下,这可以很好地满足我的需要:如果有递归 anchor/alias。 (我认为这是 YAML 1.2 规范允许的?yaml-cpp
在解析时肯定不会抛出 Exception
)例如:
first: &firstanchor
a: 5
b: *firstanchor
上面的代码将重复跟随别名到锚点,直到崩溃(SEGFAULT)可能是由于堆栈问题。如何克服这一问题?
- 是否有更好的方法遍历整个未知的
Node
结构来解决这个问题? - 有没有办法从
Node
和 public API 检查它是否是一个别名,这样我们就可以维护一个以前访问过的别名的堆栈来检测循环和中断. - 或者有没有办法获取一些唯一的
Node
标识符,以便我可以维护以前访问过的节点的堆栈? - 或者最后这个递归 anchor/alias 只是不符合规范 - 在这种情况下,我如何检查它的出现以确保我可以 return 一个适当的错误?
识别节点最明显的方法是使用它们的 m_pNode
指针;然而,这是不可公开查询的。下一个最佳方法是使用 bool Node::is(const Node& rhs) const
,遗憾的是,它不允许我们在搜索循环时进行任何排序以加快性能。
由于您可能不仅想避免循环,而且还想避免两次下降到同一个子图中,因此您实际上需要记住所有访问过的节点。这意味着最终您会将所有节点存储在您遍历的子图中,并且具有 space 复杂度 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++ 有点生疏;我希望你能理解。)