【问题标题】:Parse string of numbers解析一串数字
【发布时间】:2016-12-19 15:22:06
【问题描述】:

我需要解析几个 C 风格的字符串(大约 500k),其中包含由单个空格字符分隔的 4 个浮点数。以下是单个字符串的示例:

“90292 5879 89042.2576 5879”

我需要将这些数字存储在代表两个点的两个结构中。考虑到字符串在解析时可以修改,并且 99.99% 的数字只是无符号整数,那么最快的方法是什么?

以下是我当前的实现:

#include <iostream>
#include <cassert>
#include <chrono>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
using namespace chrono;



struct PointF
{
    float x;
    float y;
};


void parse_points(char* points, PointF& p1, PointF& p2)
{
    auto start = points;
    const auto end = start + strlen(points);

    // p1.x
    start = std::find(start, end, ' ');
    assert(start < end);
    *start = '\0';
    p1.x = static_cast<float>(atof(points));
    points = start + 1;

    // p1.y
    start = std::find(start, end, ' ');
    assert(start < end);
    *start = '\0';
    p1.y = static_cast<float>(atof(points));
    points = start + 1;

    // p2.x
    start = std::find(start, end, ' ');
    assert(start < end);
    *start = '\0';
    p2.x = static_cast<float>(atof(points));
    points = start + 1;

    // p2.y
    start = std::find(start, end, ' ');
    assert(start == end);
    p2.y = static_cast<float>(atof(points));
}



int main()
{
    const auto n = 500000;
    char points_str[] = "90292 5879 89042.2576 5879";
    PointF p1, p2;

    vector<string> data(n);

    for (auto& s : data)
        s.assign(points_str);

    const auto t0 = system_clock::now();

    for (auto i = 0; i < n; i++)
        parse_points(const_cast<char*>(data[i].c_str()), p1, p2);

    const auto t1 = system_clock::now();
    const auto elapsed = duration_cast<milliseconds>(t1 - t0).count();

    cout << "Elapsed: " << elapsed << " ms" << endl;

    cin.get();
    return 0;
}

【问题讨论】:

  • 我猜boost::lexical_castatof 快​​。
  • @sorosh_sabz 实际上慢了 8 倍以上....
  • 解析问题太多了,至少可以先搜索一下。试试这个:"stackoverflow c++ read file space separated float"
  • @Nick thx for note :)
  • 这可能高度依赖于所使用的标准库实现的精确性质,因此它可能因平台而异。

标签: c++ string parsing c++11 numbers


【解决方案1】:

给定一个带有浮点值的字符串,空格分隔:

const std::string example_input = "90292 5879 89042.2576 5879";

您应该分析一下什么更快,读取为浮点数:

std::istringstream text_stream(example_input);
std::vector<double> container;
double value;
while (text_stream >> value)
{
  container.push_back(value);
}

或读取为整数,如果有浮点指示,则会影响性能:

std::istringstream text_stream(example_input);
std::vector<double> container;
double value;
signed int int_value;
std::streampos position_before_read = text_stream.tellg();
while (text_stream >> int_value)
{
  // check the next character for possible floating point differences.
  char c;
  text_stream >> c;
  switch (c)
  {
    case '.':
    case 'E': case 'e':
      // Rewind to before the number and read as floating point
      text_stream.seekg(position_before_read);
      text_stream >> value;
      break;
    default:
      value = 1.0 * int_value;
      break;
    }
  container.push_back(value);
  position_before_read = text_stream.tellg();
}

我的猜测是标准库已针对读取浮点进行了优化,比上面的示例要好得多,并且考虑了浮点格式的所有差异

注意:或者,您可以将小数和指数读取为整数(如果存在),然后用所有三个部分构建一个浮点值。

【讨论】:

  • 您是否根据我的解决方案衡量过您的解决方案?
  • 不,您是否根据所有浮点数的简单读数来衡量您的解决方案?
【解决方案2】:

我发现代码存在多个问题(您提出的问题实际上很好):

  • 对于有数字的情况没有错误处理(注意:根据讨论,在这种情况下您期望 0)
  • 您创建 PointF 对象两次以便能够传递它们
    • 您将它们作为参考传递,因此对于阅读调用代码的人来说,这些是输出参数并非易事。
  • 您创建的解析器在 C 中可用(尽管您可以衡量它是更快还是更慢)

我建议这样做:(注意 std::experimental::optional&lt;&gt; 在这里等同于 boost::optional&lt;&gt;

#include <iostream>
#include <cstring>
#include <utility>
#include <experimental/optional>

struct PointF
{
    float x;
    float y;
};

std::experimental::optional<std::pair<PointF, PointF>> parse_points(char* pch)
{
    pch = strtok (pch, " ");
    if (pch != NULL)
    {
        float x0 = atof(pch);
        pch = strtok (NULL, " ");
        if (pch != NULL)
        {
            float y0 = atof(pch);
            pch = strtok (NULL, " ");
            if (pch != NULL)
            {
                float x1 = atof(pch);
                pch = strtok (NULL, " ");
                if (pch != NULL)
                {
                    float y1 = atof(pch);
                    PointF p0{x0, y0}, p1{x1, y1};
                    return std::make_pair(p0, p1);
                }
            }
        }
    }
    return std::experimental::nullopt;
}

int main() {
    const char str[] ="90292 5879 89042.2576 5879";
    char* pch0 = new char[sizeof(str)], *pch = pch0;
    memcpy(pch0, str, sizeof(str));

    std::experimental::optional<std::pair<PointF, PointF>> pOpt( parse_points(pch0) );
    if(pOpt)
        std::cout << pOpt->first.x  << " " << pOpt->first.y  << " "
                  << pOpt->second.x << " " << pOpt->second.y << " " << std::endl;
    delete pch;
}

【讨论】:

  • 不幸的是,std::optional 不适用于 c++11(我什至无法测试它)。您是否针对我的解决方案测试过您的解决方案?你得到了什么样的改进?
  • atof 在错误的情况下返回 0,这很好,因为它应该是默认值。除非在这种情况下不影响性能,否则始终接受清晰度。
  • @Nick:是的,缺少“等价于”部分:它是 boost::optional,C++03 也有。解决这个问题。至于atof 和默认值 0,不清楚 '0x12 AB AF xx' 应该导致 ((0,0), (0,0)) 还是(我认为)这是一个错误。另外 - 如果默认值更改(例如((-1,-1),(-1,-1)))怎么办?
【解决方案3】:

这是我的版本,没有strlen,但使用了strtok_s。 在我的机器上它需要1.1sec 而不是1.5sec

void parse_points(char* points, PointF& p1, PointF& p2)
{
    char *next_token1 = nullptr;

    // p1.x
    points = strtok_s(points, " ", &next_token1);
    p1.x = points ? static_cast<float>(atof(points)) : 0.0f;

    // p1.y
    points = strtok_s(nullptr, " ", &next_token1);
    p1.y = points ? static_cast<float>(atof(points)) : 0.0f;

    // p2.x
    points = strtok_s(nullptr, " ", &next_token1);
    p2.x = points ? static_cast<float>(atof(points)) : 0.0f;

    // p2.y
    points = strtok_s(nullptr, " ", &next_token1);
    p2.y = points ? static_cast<float>(atof(points)) : 0.0f;
}



int main()
{
    const auto n = 500000;
    char points_str[] = "90292 5879 89042.2576 5879";
    PointF p1, p2;

    vector<string> data(n);

    for (auto& s : data)
        s.assign(points_str);

    const auto t0 = system_clock::now();

    for (auto i = 0; i < n; i++)
        parse_points(const_cast<char*>(data[i].c_str()), p1, p2);

    const auto t1 = system_clock::now();
    const auto elapsed = duration_cast<milliseconds>(t1 - t0).count();

    cout << "Elapsed: " << elapsed << " ms" << endl;

    //cin.get();
    return 0;
}

【讨论】:

    【解决方案4】:

    您可以实现返回space 的位置的atof。这样,您只需要遍历每个字符串一次。

    例如。

    char *atof(char *point, float &num) {
      num = 0;
      bool neg = false, dot = false;
      float decimal = 0, mul = 0.1;
      if (*point == '-') {
        neg = true;
        point++;
      } else if (*point == '+') {
        point++;
      }
      while (*point != ' ' && *point) {
        if (*point == '.') {
          dot = true;
        } else {
          if (dot) {
            decimal += (*point - '0') * mul;
            mul *= 0.1;
          } else {
            num = num * 10 + *point - '0';
          }
        }
        point++;
      }
      if (dot) {
        num += decimal;
      }
      if (neg) {
        num = -num;
      }
      return point;
    }
    

    【讨论】:

    • 使用strtod;不要重新发明轮子。
    猜你喜欢
    • 1970-01-01
    • 2016-11-03
    • 2012-10-05
    • 1970-01-01
    • 2013-08-09
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 2012-07-30
    相关资源
    最近更新 更多