如何区分两个boost :: property_tree?

时间:2014-04-07 14:06:49

标签: c++ boost boost-propertytree

请考虑两个' boost :: property_tree' -s。

ptree1:

{
    "node1" : 1,
    "node_that_only_appears_in_this_one" : 2,
    "node3" :
    {
        "nested1" : 3,
        "nested2"
        {
            "double_nested1" : 5,
            "double_nested2" : "foo"
        }
    }
}

ptree2:

{
    "node1" : 1,
    "node3" :
    {
        "nested1" : 3,
        "nested_that_only_appears_in_this_one" : 5,
        "nested2"
        {
            "double_nested1" : 5,
            "double_nested2" : "bar"
        }
    }
}

如何编写一个我可以这样调用的通用函数:

ptree_whats_changed(ptree1, ptree2);

这样,在这种特殊情况下,返回值是另一个属性树,如下所示:

{
    "node3" :
    {
        "nested_that_only_appears_in_this_one" : 5,
        "nested2"
        {
            "double_nested2" : "foo"
        }
    }
}

从ptree1到ptree2时删除节点并不重要 - 只需查看已修改或已添加的节点就足够了。

我的理解是,property_tree将所有内容存储为一个平面列表(用。来分隔路径),这样可能比看起来更容易,而不必递归。

1 个答案:

答案 0 :(得分:5)

当前版本的Boost.Property(1.55)不提供对mathematical relations的直接支持,例如差异。但是,可以编写一个函数来迭代树并使用节点的完整路径和节点本身调用用户提供的函数。由于每次迭代都将提供节点的完整路径,因此算法可以轻松access data并使用get()put()add()构建结果。

例如,这是一个可以迭代树的函数:

/// @brief Walk a Boost.PropertyTree tree, invoking the binary function with
///        the full path and the current node.
template <typename Tree, typename Function>
void for_each(
    const Tree& tree, 
    Function fn,
    const typename Tree::path_type& parent_path = typename Tree::path_type())
{ 
  using path_type = typename Tree::path_type;
  for (auto&& value_pair: tree)
  {
    auto current_path = parent_path / path_type(value_pair.first);
    fn(current_path, value_pair.second);
    for_each(value_pair.second, fn, current_path);
  }
}

这个算法使用迭代来构造差异:

/// @brief Return tree with elements in @ref s but not in @ref t.
template <typename Tree>
Tree tree_difference(const Tree& s, const Tree& t)
{
  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding to the result when either a node in
  // 't' is not present in 's' or the node's values differ.
  for_each(s, 
    [&](const typename Tree::path_type& path, const Tree& node)
    {
      auto value = t.template get_optional<data_type>(path);
      if (!value || (value.get() != node.data()))
        result.add(path, node.data());
    });
  return result;
}

这是一个complete example,演示了两棵树之间的区别。我还添加了其他操作,例如union,intersection和symmetrical difference,以便在tree_difference()未提供确切的期望结果时展示可扩展性。

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/foreach.hpp>
#include <iostream>

namespace tree_ops {

/// @brief Walk a Boost.PropertyTree tree, invoking the binary function with
///        the full path and the current node.
template <typename Tree, typename Function>
void for_each(
    const Tree& tree, 
    Function fn,
    const typename Tree::path_type& parent_path = typename Tree::path_type())
{ 
  using path_type = typename Tree::path_type;
  for (auto&& value_pair: tree)
  {
    auto current_path = parent_path / path_type(value_pair.first);
    fn(current_path, value_pair.second);
    for_each(value_pair.second, fn, current_path);
  }
}

/// @brief Return tree with elements in @ref s but not in @ref t.
template <typename Tree>
Tree tree_difference(const Tree& s, const Tree& t)
{
  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding to the result when either a node in
  // 't' is not present in 's' or the node's values differ.
  for_each(s, 
    [&](const typename Tree::path_type& path, const Tree& node)
    {
      auto value = t.template get_optional<data_type>(path);
      if (!value || (value.get() != node.data()))
        result.add(path, node.data());
    });
  return result;
}

/// @brief Return tree with elements from both @ref s and @ref t.
template <typename Tree>
Tree tree_union(const Tree& s, const Tree& t)
{
  // The result will always contain all values in @ref s.
  Tree result = s;
  // Iterate 't', add values to the result only if the node is
  // either not in 's' or the values are different.
  for_each(t, 
    [&](const typename Tree::path_type& path, const Tree& node)
    {
      auto child = s.get_child_optional(path);
      if (!child || (child->data() != node.data()))
        result.add(path, node.data());
    });
  return result;
}

/// @brief Return tree with elements common to @ref s and @ref t.
template <typename Tree>
Tree tree_intersection(const Tree& s, const Tree& t)
{
  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding common elements found in 't' that have the same
  // value.
  for_each(s,
    [&](const typename Tree::path_type& path, const Tree& node)
    {
      auto value = t.template get_optional<data_type>(path);
      if (value && (value.get() == node.data()))
        result.add(path, node.data());
    });
  return result;
}

/// @brief Return tree with elements in either @ref s or @ref t, but not
///        both.
template <typename Tree>
Tree tree_symmetric_difference(const Tree& s, const Tree& t)
{
  return tree_difference(tree_union(s, t), tree_intersection(s, t));
}

} // namespace tree_ops

// Expose mathematical tree operations with operators.

/// @brief Return tree with elements in @ref lhs but not in @ref rhs.
boost::property_tree::ptree operator-(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)
{
  return tree_ops::tree_difference(lhs, rhs);
}

/// @brief Return tree with elements in both @ref lhs and @ref rhs.
boost::property_tree::ptree operator|(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)
{
  return tree_ops::tree_union(lhs, rhs);
}

/// @brief Return tree with elements common to @ref lhs and @ref rhs.
boost::property_tree::ptree operator&(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)
{
  return tree_ops::tree_intersection(lhs, rhs);
}

/// @brief Return tree with elements in either @ref lhs or @ref rhs, but not
///        both.
boost::property_tree::ptree operator^(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)
{
  return tree_ops::tree_symmetric_difference(lhs, rhs);
}

int main()
{
  std::istringstream json1_stream {
      "{"
      "  \"node1\" : 1,"
      "  \"node_that_only_appears_in_this_one\" : 2,"
      "  \"node3\" :"
      "  {"
      "    \"nested1\" : 3,"
      "    \"nested2\" :"
      "    {"
      "      \"double_nested1\" : 5,"
      "      \"double_nested2\" : \"foo\""
      "    }"
      "  }"
      "}"};

  std::istringstream json2_stream {
      "{"
      "  \"node1\" : 1,"
      "  \"node3\" :"
      "  {"
      "    \"nested1\" : 3,"
      "    \"nested_that_only_appears_in_this_one\" : 5,"
      "    \"nested2\" :"
      "    {"
      "      \"double_nested1\" : 5,"
      "      \"double_nested2\" : \"bar\""
      "    }"
      "  }"
      "}"};

  boost::property_tree::ptree tree1, tree2;
  read_json(json1_stream, tree1);
  read_json(json2_stream, tree2);

  std::cout << "difference in tree2 and tree1:\n";
  write_json(std::cout, tree2 - tree1);

  std::cout << "union of tree1 and tree2:\n";
  write_json(std::cout, tree1 | tree2);

  std::cout << "intersection of tree1 and tree2:\n";
  write_json(std::cout, tree1 & tree2);

  std::cout << "symmetric difference of tree1 and tree2:\n";
  write_json(std::cout, tree1 ^ tree2);
}

产生以下输出:

difference in tree2 and tree1:
{
    "node3":
    {
        "nested_that_only_appears_in_this_one": "5",
        "nested2":
        {
            "double_nested2": "bar"
        }
    }
}
union of tree1 and tree2:
{
    "node1": "1",
    "node_that_only_appears_in_this_one": "2",
    "node3":
    {
        "nested1": "3",
        "nested2":
        {
            "double_nested1": "5",
            "double_nested2": "foo",
            "double_nested2": "bar"
        },
        "nested_that_only_appears_in_this_one": "5"
    }
}
intersection of tree1 and tree2:
{
    "node1": "1",
    "node3":
    {
        "nested1": "3",
        "nested2":
        {
            "double_nested1": "5"
        }
    }
}
symmetric difference of tree1 and tree2:
{
    "node_that_only_appears_in_this_one": "2",
    "node3":
    {
        "nested2":
        {
            "double_nested2": "foo",
            "double_nested2": "bar"
        },
        "nested_that_only_appears_in_this_one": "5"
    }
}

注意:由于get_child()正在直接或间接使用,如果树具有重复键,则结果可能不具有确定性。

  

根据路径,每个级别的结果可能不完全确定,即如果多次出现相同的键,则未指定选择哪个子级。即使存在此路径的后代,这也可能导致路径无法解析。例如:

*   a -> b -> c
*     -> b
*
     

路径&#34; a.b.c&#34;如果&#34; b&#34;的决议将会成功选择第一个这样的节点,但如果选择第二个节点则失败。

更完整的算法实现可能需要迭代两个树完成,填充支持重复键的中间数据结构。然后将对中间数据结构执行操作,并根据结果构建树。