【问题标题】:Iteratively populate a BGL graph by use of a boost spirit qi parser使用 boost Spirit qi 解析器迭代地填充 BGL 图
【发布时间】:2015-04-06 08:29:05
【问题描述】:

这个问题是"Iterative update of abstract syntax tree with boost spirit"的后续问题。

已知:

  • 解析器语法允许递归

要求是:

  • 解析器的 AST 必须是 BGL 图。
  • 输入可以是每个解析器步骤的一对多符号

想法:

  • 这里有一些关于精神解析成 BGL 图的基本想法Using boost graph library: how to create a graph...,但并不完全满足要求,因为我希望能够迭代地解析一对多符号。
  • 猜测 BGL 图和精神解析器必须相互了解一些信息才能在正确的位置填充数据。首先想到的是解析器必须能够处理图顶点。
  • Using semantic actions/qi::locals 等解决方案可能适用,但我不确定这是否足以让解析器迭代地处理图。

有没有人知道如何解决这个问题,或者指出我的方向?

谢谢

【问题讨论】:

  • 我建议你这次加入一个 SSCCE。如果是后续,我真的很犹豫要从头发明一种新的语法、输入数据、图形类型和一个引人注目的演示:)
  • 可以理解。我会试试的
  • 我仍在研究解决方案...
  • 好 :) 我还在这里
  • 自从您发布答案后,事情开始变得简单多了。看起来您不再需要“解析器的 AST 必须是 BGL 图”,而可能是“解析器驱动在 BGL 图上运行的函数”。在这方面我有posted a new answer

标签: c++ boost boost-spirit boost-spirit-qi boost-graph


【解决方案1】:

简而言之:这很难做到。

如果想将一些数据解析为 BGL 图,最简单的方法可能是使用 BGL 图适配器 vector_as_graph。给定这个适配器,并且给定boost精神解析器可以解析成向量,那么它是可能的。

enum { r, s, t, u, v, w, x, y, N };
char name[] = "rstuvwxy";
typedef std::vector < std::list < int > > Graph;
Graph g(N);
g[r].push_back(v);
g[s].push_back(r);
g[s].push_back(r);
g[s].push_back(w);
g[t].push_back(x);
g[u].push_back(t);
g[w].push_back(t);
g[w].push_back(x);
g[x].push_back(y);
g[y].push_back(u);
boost::print_graph(g, name);

我使用的解决方案是在 boost Spirit 域中解析给定的语法,然后对 AST 进行转换,以便表示为 BGL 图。

最后的想法:需要注意的是,Boost Spirit 和 BGL 领域的学习曲线都相当陡峭。基于此,如果分开,可读性和实现更容易。不是说不能解决。

【讨论】:

    【解决方案2】:

    回应您的

    最后的想法:需要注意的是,boost 精神和 BGL 领域的学习曲线都相当陡峭。基于此,如果分开,可读性和实现会更容易。不是说不能解决。

    在你的回答中,我同意。保持简单的关键是分离关注点。 (Phoenix 允许您将几乎所有内容直接绑定到解析器中,但这并不意味着这是一个好主意[1]

    也许这个示例应用程序可以作为灵感?

    Live On Coliru

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/graph_utility.hpp>
    #include <sstream>
    
    using namespace boost;
    typedef adjacency_list<setS, vecS, directedS> Graph;
    
    std::vector<std::string> generate_file_data();
    void merge_into(std::istream&& is, Graph& into);
    
    int main() {
        Graph merged;
    
        for(auto& input: generate_file_data())         
            merge_into(std::istringstream(input), merged);
    
        print_graph(merged);
    }
    

    如您所见,它读取许多(不同的)文件并将边合并到merged 图中。

    当然,在一个在线演示中,我们没有输入文件,所以我在 20 个顶点 (generate_file_data()) 中生成了 5 个随机图,其中包含 6 条随机边。

    有趣的是merge_into:

    #include <boost/spirit/include/qi.hpp>
    
    void merge_into(std::istream&& is, Graph& into) {
        namespace qi  = boost::spirit::qi;
        namespace phx = boost::phoenix;
    
        phx::function<edge_merge_f> merge_edges;
        boost::spirit::istream_iterator f(is >> std::noskipws), l;
    
        bool ok = qi::phrase_parse(f, l, 
                (qi::int_ >> *qi::int_) [ merge_edges(qi::_1, qi::_2, phx::ref(into)) ] % qi::eol,
                qi::blank);
    
        assert(ok);
    }
    

    这是处理精神的唯一一段代码。如您所见,它实际上根本不了解 BGL。该逻辑留给“回调”函数对象edge_merge_f,这也很简单:

    struct edge_merge_f {
        template <typename...> struct result { typedef void type; }; // for BOOST_RESULT_OF_USE_TR1:
    
        template<typename V, typename Edges, typename G>
            void operator()(V const& v, Edges const& edges, G& into) const {
                for (auto e : edges)
                    if (!add_edge(v, e, into).second)
                        std::cout << "Duplicate edge: (" << v << "," << e << ")\n";
            }
    };
    

    更新作为奖励,一个版本通过将EdgeCallback 作为boost::function 传递给它,将merge_adjacencies 与Graph 类型完全分离:Decoupled version强>。当然,这会降低效率,但它表明了我将事物分开的意思。

    所以你有它。这是我机器上的输出(我没有为随机引擎播种,因此它是可重复的):Live On Coliru

    Duplicate edge: (9,1)
    Duplicate edge: (0,4)
    0 --> 1 2 4 9 
    1 --> 
    2 --> 
    3 --> 1 7 
    4 --> 1 
    5 --> 1 6 
    6 --> 1 3 5 
    7 --> 5 6 8 
    8 --> 3 
    9 --> 1 2 
    

    如果将 Graph typedef 更改为使用 vecS 作为边缘容器,则无需更改任何其他内容,结果改为:Live On Coliru

    0 --> 4 9 1 4 2 
    1 --> 
    2 --> 
    3 --> 7 1 
    4 --> 1 
    5 --> 1 6 
    6 --> 1 3 5 
    7 --> 5 6 8 
    8 --> 3 
    9 --> 1 2 1 
    

    [1] 我想我之前把你链接到Boost Spirit: "Semantic actions are evil"? :)

    完整列表

    为了后代(也包括generate_file_data()):

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/graph_utility.hpp>
    #include <sstream>
    
    using namespace boost;
    typedef adjacency_list<setS/*vecS*/, vecS, directedS> Graph;
    
    std::vector<std::string> generate_file_data();
    void merge_into(std::istream&& is, Graph& into);
    
    int main() {
        Graph merged;
    
        for(auto& input: generate_file_data())         
            merge_into(std::istringstream(input), merged);
    
        print_graph(merged);
    }
    
    ////////////////////////////////
    // Generating random test data
    #include <boost/graph/random.hpp>
    #include <boost/random.hpp>
    
    std::vector<std::string> generate_file_data() {
        std::vector<std::string> data;
    
        mt19937 prng(42);
        for (int i=0; i<5; ++i) {
            std::ostringstream oss;
    
            Graph g;
            generate_random_graph(g, 10, 4, prng);
    
            for (auto v : make_iterator_range(vertices(g))) {
                oss << v << " ";
                for (auto const& e : make_iterator_range(out_edges(v, g))) oss << target(e, g) << " ";
                oss << "\n";
            }
    
            data.push_back(oss.str());
        }
    
        return data;
    }
    
    ////////////////////////////////
    // Merging edges
    namespace {
        struct edge_merge_f {
            template <typename...> struct result { typedef void type; }; // for BOOST_RESULT_OF_USE_TR1:
    
            template<typename V, typename Edges, typename G>
                void operator()(V const& v, Edges const& edges, G& into) const {
                    for (auto e : edges)
                        if (!add_edge(v, e, into).second)
                            std::cout << "Duplicate edge: (" << v << "," << e << ")\n";
                }
        };
    }
    
    #include <boost/spirit/include/qi.hpp>
    
    void merge_into(std::istream&& is, Graph& into) {
        namespace qi  = boost::spirit::qi;
        namespace phx = boost::phoenix;
    
        phx::function<edge_merge_f> merge_edges;
        boost::spirit::istream_iterator f(is >> std::noskipws), l;
    
        bool ok = qi::phrase_parse(f, l, 
                (qi::int_ >> *qi::int_) [ merge_edges(qi::_1, qi::_2, phx::ref(into)) ] % qi::eol,
                qi::blank);
    
        assert(ok);
    }
    

    【讨论】:

    • 附言。现在我已经使用了我能想到的 /the/ simples 语法,但当然,这个话题可以在其他地方处理。 (例如,例如here之前)
    • 作为奖励,该版本通过将EdgeCallback 作为boost::function 传递给它,将merge_adjacencies 与Graph 类型完全分离:Decoupled version。当然,这会降低效率,但它表明了我将事物分开的意思。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    相关资源
    最近更新 更多