【问题标题】:Split a string into words by multiple delimiters [duplicate]通过多个分隔符将字符串拆分为单词
【发布时间】:2011-11-29 02:53:36
【问题描述】:

我有一些文本(有意义的文本或算术表达式),我想将其拆分为单词。
如果我有一个分隔符,我会使用:

std::stringstream stringStream(inputString);
std::string word;
while(std::getline(stringStream, word, delimiter)) 
{
    wordVector.push_back(word);
}

如何将字符串分成带有多个分隔符的标记?

【问题讨论】:

  • Boost.StringAlgorithm 或 Boost.Tokenizer 会有所帮助。
  • 或者,你可以从这个答案中得到一些想法:stackoverflow.com/questions/4888879/…
  • @K-ballo:根据问题,你不应该使用像Boost这样的外部库。
  • @MasoudM.: Boost 是否仍然算作外部库 ;) ?就我而言,Boost 就像我的标准库,是内置的!
  • @MatthieuM.:那么,Qt 对我来说也不是外部库。

标签: c++ string token delimiter


【解决方案1】:

假设其中一个定界符是换行符,下面会读取该行并通过定界符进一步拆分它。在本例中,我选择了分隔符空格、撇号和分号。

std::stringstream stringStream(inputString);
std::string line;
while(std::getline(stringStream, line)) 
{
    std::size_t prev = 0, pos;
    while ((pos = line.find_first_of(" ';", prev)) != std::string::npos)
    {
        if (pos > prev)
            wordVector.push_back(line.substr(prev, pos-prev));
        prev = pos+1;
    }
    if (prev < line.length())
        wordVector.push_back(line.substr(prev, std::string::npos));
}

【讨论】:

  • 你对我来说太快了 :p 如果换行符不是分隔符,那么只需选择一个“常规”分隔符(并将其从内部循环中删除)即可。
【解决方案2】:

如果你有提升,你可以使用:

#include <boost/algorithm/string.hpp>
std::string inputString("One!Two,Three:Four");
std::string delimiters("|,:");
std::vector<std::string> parts;
boost::split(parts, inputString, boost::is_any_of(delimiters));

【讨论】:

  • 这就像让某人打开一罐泡菜,然后你拔出电锯。
【解决方案3】:

使用std::regex

std::regex 可以在几行内进行字符串拆分:

std::regex re("[\\|,:]");
std::sregex_token_iterator first{input.begin(), input.end(), re, -1}, last;//the '-1' is what makes the regex split (-1 := what was not matched)
std::vector<std::string> tokens{first, last};

Try it yourself

【讨论】:

  • 为什么只有少数赞成票。这真是太棒了!几行,不需要外部库和新奇的东西。非常感谢!
  • @berkayberabi 没问题 - 这是一个迟到的答案,我认为这就是原因。如果您愿意,您可以发布赏金来奖励现有答案(这也会引起注意)。
  • 嗨,我没有那么高的声誉。但我还有一个问题。如果我还想根据括号拆分为分隔符([]),我该如何传递括号。如果我只是输入它们,它们将被解释为另一个正则表达式并且它不起作用
  • @berkayberabi 通过\\]逃逸
  • 这个案子怎么样? text = "Windows.Apple";我只想看 ['Windows' , 'Apple']。此正则表达式给出 [Windows,' ', Apple],其中包含我不想要的空格 (' ')。
【解决方案4】:

我不知道为什么没有人指出手动方式,但是这里是:

const std::string delims(";,:. \n\t");
inline bool isDelim(char c) {
    for (int i = 0; i < delims.size(); ++i)
        if (delims[i] == c)
            return true;
    return false;
}

在功能上:

std::stringstream stringStream(inputString);
std::string word; char c;

while (stringStream) {
    word.clear();

    // Read word
    while (!isDelim((c = stringStream.get()))) 
        word.push_back(c);
    if (c != EOF)
        stringStream.unget();

    wordVector.push_back(word);

    // Read delims
    while (isDelim((c = stringStream.get())));
    if (c != EOF)
        stringStream.unget();
}

通过这种方式,您可以根据需要对 delims 做一些有用的事情。

【讨论】:

  • 可以移动std::string word;和字符 c;在循环内部并避免使用 clear()... 变量应尽可能局部且短暂。
【解决方案5】:

如果您对如何自己做而不是使用 boost 感兴趣。

假设分隔符字符串可能很长 - 假设 M,如果它是分隔符,则检查字符串中的每个字符,每个都将花费 O(M),因此在循环中对原始字符串中的所有字符执行此操作,假设长度为 N,为 O(M*N)。

我会使用字典(比如地图 - “分隔符”到“布尔值” - 但在这里我会使用一个简单的布尔数组,每个分隔符的 index = ascii 值都为 true)。

现在迭代字符串并检查 char 是否为分隔符是 O(1),最终给我们 O(N) 的整体。

这是我的示例代码:

const int dictSize = 256;    

vector<string> tokenizeMyString(const string &s, const string &del)
{
    static bool dict[dictSize] = { false};

    vector<string> res;
    for (int i = 0; i < del.size(); ++i) {      
        dict[del[i]] = true;
    }

    string token("");
    for (auto &i : s) {
        if (dict[i]) {
            if (!token.empty()) {
                res.push_back(token);
                token.clear();
            }           
        }
        else {
            token += i;
        }
    }
    if (!token.empty()) {
        res.push_back(token);
    }
    return res;
}


int main()
{
    string delString = "MyDog:Odie, MyCat:Garfield  MyNumber:1001001";
//the delimiters are " " (space) and "," (comma) 
    vector<string> res = tokenizeMyString(delString, " ,");

    for (auto &i : res) {

        cout << "token: " << i << endl;
    }
return 0;
}

注意:tokenizeMyString 按值返回向量并首先在堆栈上创建它,因此我们在这里使用编译器的强大功能 >>> RVO - 返回值优化:)

【讨论】:

    【解决方案6】:

    在这里,很久以后,使用 C++20 的解决方案:

    constexpr std::string_view words{"Hello-_-C++-_-20-_-!"};
    constexpr std::string_view delimeters{"-_-"};
    for (const std::string_view word : std::views::split(words, delimeters)) {
        std::cout << std::quoted(word) << ' ';
    }
    // outputs: Hello C++ 20!
    

    必需的标题:

    #include <ranges>
    #include <string_view>
    

    参考:https://en.cppreference.com/w/cpp/ranges/split_view

    【讨论】:

      【解决方案7】:

      使用 Eric Niebler 的 range-v3 库:

      https://godbolt.org/z/ZnxfSa

      #include <string>
      #include <iostream>
      #include "range/v3/all.hpp"
      
      int main()
      {
          std::string s = "user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3";
          auto words = s  
              | ranges::view::split('|')
              | ranges::view::transform([](auto w){
                  return w | ranges::view::split(':');
              });
            ranges::for_each(words, [](auto i){ std::cout << i  << "\n"; });
      }
      

      【讨论】:

      猜你喜欢
      • 2013-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-07
      • 1970-01-01
      • 2022-01-18
      相关资源
      最近更新 更多