Boost 属性树:删除嵌套节点

Posted

技术标签:

【中文标题】Boost 属性树:删除嵌套节点【英文标题】:Boost property tree: Remove a nested node 【发布时间】:2014-04-22 10:31:46 【问题描述】:

假设我有以下树:

boost::property_tree::ptree tree;
tree.add("1.2.3", "value-1");
tree.add("1.2.3.4", "nested-value");
tree.add("1.2.3", "value-2");
tree.add("1.2.33", "other-value");

具有以下序列化的 INFO 形式:

1

    2
    
        3 value-1
        
            4 nested-value
        
        3 value-2
        33 other-value
    

是否有一种方法可以删除具有提供(可能是嵌套)路径的所有节点? 即:

remove(tree, "1.2.3");
BOOST_ASSERT(!tree.get_optional<std::string>("1.2.3") && 
             !tree.get_child_optional("1.2.3"));

带有结果信息表单:

1

    2
    
        33 other-value
    

查看 ptree 文档和源代码,我发现了几种删除树的直接子级的方法(不考虑嵌套子级)。此外,有几种方法可以通过它的完整路径获取子树(即使它是嵌套的)。但由于无法轻松获取节点的父节点,因此我无法将所有这些组合起来得到我需要的东西。

有没有什么简单的方法可以得到我需要的东西,可能不需要重新实现树遍历?

【问题讨论】:

看看 boost::adjacency_list 和 boost::default_dfs_visitor - 比 property_tree 复杂但更强大 谢谢,但我还需要序列化/反序列化树的能力。 【参考方案1】:

我认为这是不可能的。这在 JSON 中会很好,但是使用 INFO 子树键可以在每个级别重复,因此遍历所有树很重要

也许这个答案有助于开始:How to iterate over XML structure in boost::property_tree

但要非常小心迭代正在修改的树。您需要仔细检查 erase 文档中的迭代器失效规则

【讨论】:

您能否详细说明在问题的上下文中 JSON 有什么不同?我的示例中的树无法序列化为 JSON,但这可能不是因为嵌套,而是因为一个节点同时具有值​​和子树。如果我删除,例如,添加节点 1.2.3.4 的代码行,那么树会在 JSON 中完美地序列化。 我从来没有说过关于嵌套的事情。事实上,我说过重复的子键是 JSON 中的一个问题,这与阻止您执行单个 get_child_optional + erase 的问题完全相同。 “额外功能”复合,因为您必须检查多值键每个级别1.2 也可能重复) 可以使用 ptree 序列化程序获取例如以下 JSON: "1": "2": "3": "value-1" , "2": "value -1" ,它有一个重复的子键。它是无效的 JSON 吗? @AlexChe 这有什么关系?根据RFC4627:"The names within an object SHOULD be unique",它是无效的 JSON。 RFC2119 解释:"SHOULD This word, or the adjective "RECOMMENDED", mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course" 现在我想我明白你的意思了。 RFC 建议 JSON 的所有直接子键必须是唯一的。如果没有这种唯一性,我们无法确定 get_child_optional 返回的节点是否是包含我们要删除的子节点的确切父节点。实际上,您已经在答案中写了这个,但我对 JSON 的提及感到困惑和分心,因为我认为存储格式与问题无关。 (在我的问题中,我只提供了 INFO 表单作为视觉效果。)【参考方案2】:

如果有人需要,我是这样做的(抱歉,没有 C++11):

template <typename TTree>
boost::optional<TTree&> get_parent_optional(TTree& tree, 
                                            typename TTree::path_type path)

   if (path.empty())
      return boost::optional<TTree&>();

   TTree::key_type root = path.reduce();

   if (path.empty())
   
      return (tree.find(root) != tree.not_found()) ? 
         boost::optional<TTree&>(tree) : boost::optional<TTree&>(); 
   

   std::pair<TTree::assoc_iterator, TTree::assoc_iterator> range = 
      tree.equal_range(root);

   for (TTree::assoc_iterator it = range.first; it != range.second; ++it)
   
      boost::optional<TTree&> result = get_parent_optional(it->second, path);
      if (result)
         return result;
   

   return boost::optional<TTree&>();


template <typename TTree>
boost::optional<TTree&> get_parent_optional(TTree& tree, 
                                            const typename TTree::key_type & path)

   return get_parent_optional(tree, TTree::path_type(path));


template <typename TTree>
boost::optional<TTree&> get_parent_optional(TTree& tree, 
                                            const char * path)

   return get_parent_optional(tree, std::string(path));


template <typename TTree>
typename TTree::key_type get_last_fragment(const typename TTree::key_type & keyPath)

   TTree::path_type path(keyPath);

   if (path.empty())
      return TTree::key_type(); // or exception

   while (!path.single())
      path.reduce();

   return path.reduce();


template <typename TTree>
void erase(TTree & tree, const typename TTree::key_type & path)

   boost::optional<TTree&> parent;
   typename TTree::key_type subkey = get_last_fragment<TTree>(path);
   while (parent = get_parent_optional(tree, path))
   
      parent->erase(subkey);
   

请注意,如果要删除多个分支,则在每次删除分支后都会重复一棵树。如果是一棵大树,这可能是个问题。

【讨论】:

【参考方案3】:

受 Alex Che 的回答启发,这是我的解决方案。它假设没有重复的键,也没有包含数据和子节点的键。它返回一个表示成功的布尔值。它还可以选择删除为空的父键,而不是为键留下空值。

template <typename PTree> bool erasePath(PTree &inout_tree, const typename PTree::key_type &in_keyPath, bool in_removeEmptyKeys) try PTree *subTree = &inout_tree; typename PTree::path_type path(in_keyPath); typename PTree::key_type parentPath; typename PTree::key_type subKey; while (!path.single()) subKey = path.reduce(); parentPath = parentPath.empty() ? subKey : parentPath + path.separator() + subKey; subTree = &(subTree->get_child(subKey)); subKey = path.reduce(); if ( subTree->erase(subKey) == 0 ) return false; if (in_removeEmptyKeys && subTree->empty() && !parentPath.empty()) return erasePath(inout_tree, parentPath); return true; catch (std::exception &) return false;

【讨论】:

以上是关于Boost 属性树:删除嵌套节点的主要内容,如果未能解决你的问题,请参考以下文章

如何重命名提升属性树中的节点/元素?

设备树基本语法-18

Boost的属性树:删除一个知道其路径的孩子

Angular Material - 扁平树和嵌套树之间的区别

红黑树之删除节点

红黑树插入和删除的情况分析