解析一个浮点数的C字符串

Nic*_*ick 5 c++ string parsing

我有一个C字符串,其中包含由逗号和空格分隔的浮点数列表.每对数字由一个(或多个)空格分隔,并表示x和y字段由逗号分隔的点(并且可选地由空格分隔).

" 10,9 2.5, 3   4 ,150.32 "
Run Code Online (Sandbox Code Playgroud)

我需要解析这个字符串以填充列表Point(x, y).
以下是我目前的实施:

const char* strPoints = getString();
std::istringstream sstream(strPoints);

float x, y;
char comma;

while (sstream >> x >> comma >> y)
{
   myList.push(Point(x, y));
}
Run Code Online (Sandbox Code Playgroud)

由于我需要解析很多(最多500,000)这些字符串,我想知道是否有更快的解决方案.

seh*_*ehe 5

看看提升精神:

它支持NaN,正负无限就好了.它还允许您简洁地表达约束语法.

  1. 简单的代码修改

    以下是您的语法的改编样本:

    struct Point { float x,y; };
    typedef std::vector<Point> data_t;
    
    // And later:
    bool ok = phrase_parse(f,l,*(double_ > ',' > double_), space, data);
    
    Run Code Online (Sandbox Code Playgroud)

    迭代器可以是任何迭代器.所以你可以用C字符串将它连接起来就好了.

    这是对链接基准案例的直接调整.这将向您展示如何从任何std::istream 直接从内存映射文件解析.

    Live On Coliru


  2. 进一步优化(严格来说是C字符串)

    这是一个不需要知道前面字符串长度的版本(这很简洁,因为strlen如果没有可用的长度,它会避免调用):

    template <typename OI>
    static inline void parse_points(OI out, char const* it, char const* last = std::numeric_limits<char const*>::max()) {
        namespace qi  = boost::spirit::qi;
        namespace phx = boost::phoenix;
    
        bool ok = qi::phrase_parse(it, last,
                *(qi::double_ >> ',' >> qi::double_) [ *phx::ref(out) = phx::construct<Point>(qi::_1, qi::_2) ],
                qi::space);
    
        if (!ok || !(it == last || *it == '\0')) {
            throw it; // TODO proper error reporting?
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意我是如何使用输出迭代器的,以便您决定如何累积结果.对向量/ /解析向量的明显包装器将是:

    static inline data_t parse_points(char const* szInput) {
        data_t pts;
        parse_points(back_inserter(pts), szInput);
        return pts;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但是你也可以做不同的事情(比如附加到现有容器,可能预先保留已知容量等).这样的事情通常允许真正优化的集成.

    这段代码在~30行基本代码中完全演示:

    Live On Coliru

  3. 额外的令人敬畏的奖金

    展示这个解析器的灵活性; 如果你只是想检查输入并得到点的计数,你可以用一个简单的lambda函数替换输出迭代器,该函数递增计数器而不是添加新构造的点.

    int main() {
        int count = 0;
        parse_points( " 10,9 2.5, 3   4 ,150.32    ", boost::make_function_output_iterator([&](Point const&){count++;}));
        std::cout << "elements in sample: " << count << "\n";
    }
    
    Run Code Online (Sandbox Code Playgroud)

    Live On Coliru

    由于所有内容都是内联的,编译器会注意到整个Point不需要在这里构造并消除该代码:http://paste.ubuntu.com/9781055/

    可以看到main函数直接调用非常解析器原语.手动编码解析器不会让你在这里进行更好的调整,至少不需要付出很多努力.

  • 很好,我没想到Spirit会那么快. (2认同)