Boost::Spirit::QI解析器:解析元素的索引

5
使用Boost::Spirit::QI,能否从逗号分隔的字符串中解析数字,以便获得每个解析数字的索引?
假设我有一个字符串"23,123,65,1",我想将这些数字插入到给定位置(0、1、2、3)的矩阵中。一种方法是将数字解析为std::vector,然后将它们复制到矩阵行中,但这并不特别快。
目前我正在使用向量变体:
Matrix data(10, 4);
int row = 0;
int col = 0;
std::string str = "23,123,65,1";
std::vector<double> res;
if (qi::parse(str.begin(), str.end(), qi::double_ % ',', res))
{
  std::for_each(res.begin(), res.end(), [&col, &data, &row](double elem) {

      data(row, col) = elem;
      col++;
});
}

如果解析器具有接受lambda函数或类似功能的成功回调,那将是非常棒的。
1个回答

12
有多种方法。
我通常建议使用经过深思熟虑的repeat(n)表达式,直接暴露容器属性(例如vector<vector<double>>)。
你似乎在寻找具有状态的语义动作。 (这是从lex / yacc常见的做法)。
我在下面的三个完整演示中详细介绍了这些方法(1、2和3)。
一种高级技术是使用自定义点来允许Spirit将您的Matrix类型直接处理为容器属性,并使用spirit :: traits覆盖其插入逻辑。 对于这种方法,我参考了此答案:pass attribute to child rule in boost spirit

使用继承属性

这里有一个相对简单的方法:

  1. parsing directly into a vector<vector<double> > (full code live online)

    qi::rule<It, Matrix::value_type(size_t cols), qi::blank_type> row;
    qi::rule<It, Matrix(size_t rows,size_t cols), qi::blank_type> matrix;
    
    row    %= skip(char_(" \t,")) [ repeat(_r1) [ double_ ] ];
    matrix %= eps // [ std::cout << phx::val("debug: ") << _r1 << ", " << _r2 << "\n" ]
           >> repeat(_r1) [ row(_r2) >> (eol|eoi) ];
    

    Usage:

    if (qi::phrase_parse(f,l,parser(10, 4),qi::blank, m))
        std::cout << "Wokay\n";
    else
        std::cerr << "Uhoh\n";
    
  2. Similarly, but adapting a Matrix struct (full code live here)

    struct Matrix
    {
        Matrix(size_t rows, size_t cols) : _cells(), _rows(rows), _cols(cols) { }
    
        double       & data(size_t col, size_t row)       { return _cells.at(row).at(col); } 
        const double & data(size_t col, size_t row) const { return _cells.at(row).at(col); } 
    
        size_t columns() const { return _cols; }
        size_t rows()    const { return _rows; }
    
        std::vector<std::vector<double> > _cells;
        size_t _rows, _cols;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Matrix, (std::vector<std::vector<double> >,_cells))
    

    Usage

    Matrix m(10, 4);
    
    if (qi::phrase_parse(f,l,parser(m.rows(),m.columns()),qi::blank, m))
        std::cout << "Wokay\n";
    else
        std::cerr << "Uhoh\n";
    

使用语义动作和qi::locals

3. 这种方法需要更多的工作,但可能更加灵活。你需要定义一个多态可调用类型,以在给定的单元格中插入一个值:

struct MatrixInsert
{
    template <typename, typename, typename, typename> struct result { typedef bool type; };
    template <typename Matrix, typename Row, typename Col, typename Value>
        bool operator()(Matrix &m, Row& r, Col& c, Value v) const
        {
            if (r < m.rows() && c < m.columns())
            {
                m.data(r, c++) = v;
                return true; // parse continues
            }
            return false;    // fail the parse
        }
};

BOOST_PHOENIX_ADAPT_CALLABLE(matrix_insert, MatrixInsert, 4)

最后一行将其变成了一个phoenix的惰性函数,因此您可以在语义动作中使用它而无需使用奇怪的绑定语法:
qi::rule<It, Matrix(), qi::blank_type, qi::locals<size_t /*_a: row*/, size_t/*_b: col*/> > matrix;
matrix = eps    [ _a = 0 /*current row*/ ]
     >> (
            eps     [ _b = 0 /*current col*/ ] 
         >> double_ [ _pass = matrix_insert(_val, _a, _b, _1) ] % ','
        ) % (eol    [ ++_a /*next row*/])
     ;

完整的代码在这里,再次提醒您可以在 liveworkspace.org上实时运行

2
你是个巫师吗?第三个选项正是我所需要的,我从未想过可以做到这样(它与Eigen的矩阵非常完美地配合)。但愿有一个选项可以让我多次点赞你。非常感谢。 - SiimKallas
非常感谢。我很喜欢在SO上练习我的C++技能 :) - sehe
以防你感兴趣 - 你的版本加速了代码2.6倍! - SiimKallas
哇,我可以想象这是因为以前你必须大量复制 :) - sehe
@gnzlbg 真是令人难过 :( 但我相信这些代码片段捕捉到了关键的部分。 - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接