遍历多级提升树
Iterate through multilevel boost tree
我的树看起来像这样:
{
"Library":
{
"L_ID": "1",
"Book":
{
"B_ID": "1",
"Title": "Moby Dick"
},
"Book":
{
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library":
{
"L_ID": "2",
"Book":
{
"B_ID": "1",
"Title": "Velocity"
},
"Book":
{
"B_ID": "2",
"Title": "Creeper"
}
}
}
我想要做的是遍历库。当我找到我正在寻找的 L_ID 时,遍历书籍直到找到我正在寻找的 B_ID。那时,我想访问该部分中的所有叶子。
IE。寻找图书馆 2,书 1,书名
注意:可能有比这更好的方法。
boost::property_tree::ptree libraries = config_.get_child("Library");
for (const auto &lib : libraries)
{
if (lib.second.get<uint16_6>("L_ID") == 2)
{
//at this point, i know i'm the correct library...
boost::property_tree::ptree books = lib.get_child("Book");
for (const auto &book : books)
{
if (book.second.get<uint16_t>("B_ID") == 1)
{
std::string mybook = book.second.get<std::string>("Title");
}
}
}
我一尝试查看我的第一个子树就失败了。这里出了什么问题??
首先,"JSON" 存在严重缺陷。至少修复缺少的引号和逗号:
{
"Library": {
"L_ID": "1",
"Book": {
"B_ID": "1",
"Title": "Moby Dick"
},
"Book": {
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library": {
"L_ID": "2",
"Book": {
"B_ID": "1",
"Title": "Velocity"
},
"Book": {
"B_ID": "2",
"Title": "Creeper"
}
}
}
接下来,你好像有点糊涂了。 get_child("Library")
以该名称获取第一个子节点,而不是包含名为 "Library" 的子节点的节点(顺便说一下,这将是根节点)。
我可以建议添加一些抽象,也许还有一些设施供某些人查询 names/properties:
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
如您所见,我们假设一个 Config
类型可以找到一个库:
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
什么是libraries()
?我们将深入研究它,但让我们先看一下:
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
那个魔法应该读作"give me all nodes that are named Library, which have a L_ID property but wrap them in a Library object"。现在跳过细节,让我们看一下 Library
对象,它显然 知道 books()
:
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
我们在 books()
和 book(id)
中看到相同的模式来查找特定项目。
魔法
魔法使用 Boost Range 适配器并潜伏在 PtreeTools
:
namespace PtreeTools {
namespace detail {
// ...
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
这看似简单,对吧。那是因为我们站在 Boost Range 的肩膀上:
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
接下来,我们只定义知道如何过滤特定ptree节点的谓词:
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
还有一个要投射到我们的包装器对象的转换:
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
完整的现场演示
#include <boost/property_tree/json_parser.hpp>
#include <boost/range/adaptors.hpp>
using boost::property_tree::ptree;
namespace PtreeTools {
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
struct Config {
ptree data_;
using Id = uint16_t;
struct Book {
ptree const& data_;
Id id() const { return data_.get<Id>("B_ID"); }
std::string title() const { return data_.get<std::string>("Title"); }
};
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
};
#include <iostream>
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
打印:
Book title: Velocity
@Sehe 修正了你的 JSON 在句法上是正确的,但我认为比这更进一步是有意义的。鉴于您所代表的数据,伟大 拥有一系列图书馆会更有意义,每个图书馆都包含一系列书籍,提供如下数据:
{
"Libraries": [
{
"L_ID": 1,
"Books": [
{
"B_ID": 1,
"Title": "Moby Dick"
},
{
"B_ID": 2,
"Title": "Jurassic Park"
}
]
},
{
"L_ID": 2,
"Books": [
{
"B_ID": 1,
"Title": "Velocity"
},
{
"B_ID": 2,
"Title": "Creeper"
}
]
}
]
}
然后,如果可能的话,我会选择一个真正适合手头工作的库。 Boost 属性 树并不是真正打算用作 general-purpose JSON 库。如果你真的坚持,你可以将它推入那个角色,但至少对于你在问题中概述的内容,它会让你通过相当多的额外工作来获得你想要的东西。
就个人而言,我可能会改用 nlohmann's JSON library。使用它,我们可以更直接地进行一些解决方案。基本上,一旦它解析了一个 JSON 文件,我们就可以像处理标准库中的正常 collections 一样处理结果——我们可以使用所有正常的算法,range-based for
循环,依此类推。所以,我们可以加载一个 JSON 文件,类似于:
using json=nlohmann::json;
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
然后我们可以使用如下代码在库中查找特定 ID 号:
for (auto const &lib : libs["Libraries"])
if (lib["L_ID"] == lib_num)
// we've found the library we want
查找特定书籍几乎相同:
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
// we've found the book we want
从那里,打印出的标题看起来像这样:std::cout << book["Title"];
将它们放在一起,我们最终可以得到如下代码:
#include "json.hpp"
#include <fstream>
#include <iostream>
using json = nlohmann::json;
std::string find_title(json lib_data, int lib_num, int book_num) {
for (auto const &lib : lib_data["Libraries"])
if (lib["L_ID"] == lib_num)
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
return book["Title"];
return "";
}
int main() {
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
std::cout << find_title(lib_data, 1, 2);
}
如果您真的想将每个 JSON object 转换为 C++ object,您也可以很容易地做到这一点。它看起来像这样:
namespace library_stuff {
struct Book {
int B_ID;
std::string title;
};
void from_json(json &j, Book &b) {
b.B_ID = j["B_ID"];
b.title = j["Title"];
}
}
所以这里有两点:您必须将函数命名为 from_json
,并且它必须在与其关联的 struct/class 相同的命名空间中定义。有了这个脚手架,我们可以通过简单的赋值从 JSON 转换为结构,如下所示:
book b = lib_data["Libraries"][0]["Books"][1];
我认为这在这种特殊情况下是否真的有用非常值得怀疑。
我的树看起来像这样:
{
"Library":
{
"L_ID": "1",
"Book":
{
"B_ID": "1",
"Title": "Moby Dick"
},
"Book":
{
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library":
{
"L_ID": "2",
"Book":
{
"B_ID": "1",
"Title": "Velocity"
},
"Book":
{
"B_ID": "2",
"Title": "Creeper"
}
}
}
我想要做的是遍历库。当我找到我正在寻找的 L_ID 时,遍历书籍直到找到我正在寻找的 B_ID。那时,我想访问该部分中的所有叶子。 IE。寻找图书馆 2,书 1,书名 注意:可能有比这更好的方法。
boost::property_tree::ptree libraries = config_.get_child("Library");
for (const auto &lib : libraries)
{
if (lib.second.get<uint16_6>("L_ID") == 2)
{
//at this point, i know i'm the correct library...
boost::property_tree::ptree books = lib.get_child("Book");
for (const auto &book : books)
{
if (book.second.get<uint16_t>("B_ID") == 1)
{
std::string mybook = book.second.get<std::string>("Title");
}
}
}
我一尝试查看我的第一个子树就失败了。这里出了什么问题??
首先,"JSON" 存在严重缺陷。至少修复缺少的引号和逗号:
{
"Library": {
"L_ID": "1",
"Book": {
"B_ID": "1",
"Title": "Moby Dick"
},
"Book": {
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library": {
"L_ID": "2",
"Book": {
"B_ID": "1",
"Title": "Velocity"
},
"Book": {
"B_ID": "2",
"Title": "Creeper"
}
}
}
接下来,你好像有点糊涂了。 get_child("Library")
以该名称获取第一个子节点,而不是包含名为 "Library" 的子节点的节点(顺便说一下,这将是根节点)。
我可以建议添加一些抽象,也许还有一些设施供某些人查询 names/properties:
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
如您所见,我们假设一个 Config
类型可以找到一个库:
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
什么是libraries()
?我们将深入研究它,但让我们先看一下:
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
那个魔法应该读作"give me all nodes that are named Library, which have a L_ID property but wrap them in a Library object"。现在跳过细节,让我们看一下 Library
对象,它显然 知道 books()
:
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
我们在 books()
和 book(id)
中看到相同的模式来查找特定项目。
魔法
魔法使用 Boost Range 适配器并潜伏在 PtreeTools
:
namespace PtreeTools {
namespace detail {
// ...
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
这看似简单,对吧。那是因为我们站在 Boost Range 的肩膀上:
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
接下来,我们只定义知道如何过滤特定ptree节点的谓词:
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
还有一个要投射到我们的包装器对象的转换:
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
完整的现场演示
#include <boost/property_tree/json_parser.hpp>
#include <boost/range/adaptors.hpp>
using boost::property_tree::ptree;
namespace PtreeTools {
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
struct Config {
ptree data_;
using Id = uint16_t;
struct Book {
ptree const& data_;
Id id() const { return data_.get<Id>("B_ID"); }
std::string title() const { return data_.get<std::string>("Title"); }
};
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
};
#include <iostream>
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
打印:
Book title: Velocity
@Sehe 修正了你的 JSON 在句法上是正确的,但我认为比这更进一步是有意义的。鉴于您所代表的数据,伟大 拥有一系列图书馆会更有意义,每个图书馆都包含一系列书籍,提供如下数据:
{
"Libraries": [
{
"L_ID": 1,
"Books": [
{
"B_ID": 1,
"Title": "Moby Dick"
},
{
"B_ID": 2,
"Title": "Jurassic Park"
}
]
},
{
"L_ID": 2,
"Books": [
{
"B_ID": 1,
"Title": "Velocity"
},
{
"B_ID": 2,
"Title": "Creeper"
}
]
}
]
}
然后,如果可能的话,我会选择一个真正适合手头工作的库。 Boost 属性 树并不是真正打算用作 general-purpose JSON 库。如果你真的坚持,你可以将它推入那个角色,但至少对于你在问题中概述的内容,它会让你通过相当多的额外工作来获得你想要的东西。
就个人而言,我可能会改用 nlohmann's JSON library。使用它,我们可以更直接地进行一些解决方案。基本上,一旦它解析了一个 JSON 文件,我们就可以像处理标准库中的正常 collections 一样处理结果——我们可以使用所有正常的算法,range-based for
循环,依此类推。所以,我们可以加载一个 JSON 文件,类似于:
using json=nlohmann::json;
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
然后我们可以使用如下代码在库中查找特定 ID 号:
for (auto const &lib : libs["Libraries"])
if (lib["L_ID"] == lib_num)
// we've found the library we want
查找特定书籍几乎相同:
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
// we've found the book we want
从那里,打印出的标题看起来像这样:std::cout << book["Title"];
将它们放在一起,我们最终可以得到如下代码:
#include "json.hpp"
#include <fstream>
#include <iostream>
using json = nlohmann::json;
std::string find_title(json lib_data, int lib_num, int book_num) {
for (auto const &lib : lib_data["Libraries"])
if (lib["L_ID"] == lib_num)
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
return book["Title"];
return "";
}
int main() {
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
std::cout << find_title(lib_data, 1, 2);
}
如果您真的想将每个 JSON object 转换为 C++ object,您也可以很容易地做到这一点。它看起来像这样:
namespace library_stuff {
struct Book {
int B_ID;
std::string title;
};
void from_json(json &j, Book &b) {
b.B_ID = j["B_ID"];
b.title = j["Title"];
}
}
所以这里有两点:您必须将函数命名为 from_json
,并且它必须在与其关联的 struct/class 相同的命名空间中定义。有了这个脚手架,我们可以通过简单的赋值从 JSON 转换为结构,如下所示:
book b = lib_data["Libraries"][0]["Books"][1];
我认为这在这种特殊情况下是否真的有用非常值得怀疑。