【问题标题】:How to create a general purpose data tokenizer in C++?如何在 C++ 中创建通用数据标记器?
【发布时间】:2016-01-10 02:55:57
【问题描述】:

所以,假设我有一个数据文件,其中包含与此类似的常规数据:

[42,6,9,56,1337]
[220,9001,15,22,35]
[127,0,0,1,8080]

我将每一行作为字符串读取,并且我有一个标记器,它接受一个输入字符串,多个分隔符作为另一个字符串,以及一个对 vector<string> 的引用,用于将输出存储到。

// given a string with delimiters inside, parse it into
//  individual tokens stored in a vector<string>
void tokenize(const string& str, vector<string>& tokens,
              const string& delimiters = " ") {
  auto last_pos = str.find_first_not_of(delimiters, 0);      // first token
  auto curr_pos = str.find_first_of(delimiters, last_pos);   // next delim

  while (curr_pos != str_end || last_pos != str_end) {
    tokens.emplace_back(str.substr(last_pos, curr_pos - last_pos));    
    last_pos = str.find_first_not_of(delimiters, curr_pos);  // next token
    curr_pos = str.find_first_of(delimiters, last_pos);      // next delim
  }
}

int main() {
  ifstream fs{"data"};
  string tmp{""};
  const string delims{"[,]"};
  vector<string> tokens;
  //vector<int> tokens;
  //vector<double> tokens;

  while (getline(fs, tmp)) tokenize(tmp, tokens, delims);

  cout << tokens << endl;
}

到目前为止还可以。但后来我想使用实际的数据类型而不是字符串,所以我编写了几个数字包装函数,它们采用vector&lt;string&gt; 并将其转换为(比如)vector&lt;int&gt;。然后我意识到这些基本上是彼此的重复。

// int wrapper
void tokenize(const string& str, vector<int>& tokens,
              const string& delimiters = " ") {
  vector<string> str_tokens;
  tokenize(str, str_tokens, delims);

  for (const auto& e : str_tokens)
    tokens.emplace_back(stoi(e));  // ints    
}

然后我尝试创建另一个通用包装器,但我对以下问题感到困惑 A) 我不确定如何在标准库转换函数之间进行更改,并且 B) 认为它也会尝试使用字符串 T 来执行,这不是最初的想法。

经过进一步思考,我意识到我可能只是做错了,应该以某种方式尝试只使用一个通用函数。但我不知所措。

这是程序清单。数据存储为一个名为“data”的本地文件。 http://pastebin.com/dRAXRWa3

【问题讨论】:

  • 有任何理由编写自己的分词器吗?为什么不直接使用库,比如 boost::spirit?
  • 您可以尝试阅读 awk 或 CSV 解析器的源代码,因为这些是用 C 或 C++ 编写的通用数据标记器的示例。 C 与 C++ 不同,但我相信代码会有所帮助。
  • @Rostislav 因为我正在尝试学习创建自己的 C++ 程序的细节。
  • @djechlin 谢谢,感谢您的建议。我会考虑你的建议。
  • 有道理。我只是想确定:)

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


【解决方案1】:

这是模板发挥作用的典型示例。唯一的罪魁祸首是您需要调用不同的函数来将字符串转换为数据类型。不过,这也可以通过模板来解决。这是一个有效的注释示例:

#include <iostream>
#include <iomanip>

#include <vector>
#include <algorithm>
#include <string>

using namespace std;

// Declare a generic conversion function...
template<typename T>
T stoT(const std::string& s);

// ... and specialize it for the data types you need to convert
// int specialization
template<>
int stoT(const std::string& s)
{
    return stoi(s);
}

// double specialization
template<>
double stoT(const std::string& s)
{
    return stod(s);
}

template<typename T>
void tokenize(const string& str, vector<T>& tokens,
              const string& delimiters = " ") {
    vector<string> str_tokens = {"1", "2", "3"};

    // Prepare the output - clear and reserve the space to avoid multiple allocations
    str_tokens.clear();
    tokens.reserve(str_tokens.size());

    // Transform the strings to your data types
    std::transform(str_tokens.begin(), str_tokens.end(), std::back_inserter(tokens), stoT<T>);

}

int main()
{
    std::vector<int> vi;
    tokenize("", vi); 
    for (const auto& v : vi) { std::cout << v << " "; }

    std::cout << "\n";

    std::vector<double> vd;
    tokenize("", vd); 
    std::cout << std::fixed;
    for (const auto& v : vd) { std::cout << std::setprecision(2) << v << " "; }
}

【讨论】:

  • 非常感谢,太好了!我将使用您的示例来改进我自己提出的类似的解决方案。
【解决方案2】:

所以一个朋友将我链接到 isocpp 的这个页面:template specialization,我能够想出我自己的可行方法(尽管 Rostislav 的显然更好)。

我创建了一个

T decode<T>(const string& x) { }

一组特化,然后模板化 tokenizer() 函数并更改一行代码。

tokens.emplace_back(decode<T>(str.substr(last_pos, curr_pos - last_pos)));

它似乎按我的预期工作。现在我会根据你的建议改进它。

谢谢。

(编辑)这是一个更正的版本。 http://pastebin.com/reRMc2G3

【讨论】:

    猜你喜欢
    • 2018-10-02
    • 2022-11-09
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多