【问题标题】:Iterate through multilevel boost tree遍历多级提升树
【发布时间】:2017-12-25 04:59:37
【问题描述】:

我的树看起来像这样:

{
"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");
            }
        }
   }

我一尝试查看我的第一个子树就失败了。这里出了什么问题??

【问题讨论】:

    标签: boost tree iteration ptree


    【解决方案1】:

    对于初学者来说,“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") 按该名称获取第一个子节点,而不是包含名为“库”的子节点的节点(顺便说一下,这将是根节点)。

    我可以建议添加一些抽象,或者一些工具来通过某些名称/属性进行查询:

    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>();
    }
    

    这个魔法应该被理解为“给我所有名为 Library 的节点,它们具有 L_ID 属性,但将它们包装在 Library 对象中”。现在跳过细节,让我们看看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 };
                }
            };
    }
    

    完整的现场演示

    Live On Coliru

    #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
    

    【讨论】:

    • 对于初学者,我对 json 表示歉意。我还没有弄清楚如何在 SO 中复制/过去的代码块并保持格式。我最终用手打字,这很痛苦。我只使用 JSON 几个小时,所以我认为我对不正确的属性树行为做了一些假设。您的解决方案正是我想要的。
    【解决方案2】:

    @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 属性树并不是真正打算用作通用 JSON 库。如果您真的坚持,您可以将其推入该角色,但至少对于您在问题中概述的内容,它会让您通过相当多的额外工作来获得您想要的。

    就个人而言,我可能会改用nlohmann's JSON library。使用它,我们可以更直接地解决问题。基本上,一旦解析了 JSON 文件,我们就可以像对待标准库中的普通集合一样处理结果——我们可以使用所有普通算法、基于范围的 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 &lt;&lt; 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 对象转换为 C++ 对象,您也可以相当轻松地做到这一点。它看起来像这样:

    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,并且它必须定义在与其关联的结构/类相同的命名空间中。有了这个脚手架,我们可以通过简单的赋值将 JSON 转换为 struct,如下所示:

    book b = lib_data["Libraries"][0]["Books"][1];
    

    我认为这在这种特殊情况下是否真的有用是非常值得怀疑的。

    【讨论】:

    • 我没有将 JSON 更改为任何“合理”的形式 :) 我只是添加了 8 个左右缺少的开头引号,可能还有 2 个逗号
    • 抱歉我的描述有误——我相信我已经更正了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-28
    相关资源
    最近更新 更多