使用多个分隔符将字符串拆分为单词

42

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

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

如何使用多个分隔符将字符串分割成标记?


或者,您可以从此答案中获得一些想法:https://dev59.com/Mm445IYBdhLWcg3wZJUn - Nawaz
3
根据问题,你不应使用像Boost这样的外部库。 - masoud
1
@MasoudM:Boost 仍然算作外部库吗?就我而言,Boost 就像我的标准库一样,它是内置的! - Matthieu M.
1
@MatthieuM:那么,对我来说Qt也不是外部库。 - masoud
感谢您提供的关于频率计数的链接! :) - Sergei G
显示剩余2条评论
7个回答

56
假设其中一个分隔符是换行符,以下代码读取该行并进一步按照空格、单引号和分号进行分割。
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));
}

4
你太快了 :p 如果换行符不是分隔符,那么选择一个“常规”的分隔符(并将其从内部循环中删除)就可以了。 - Matthieu M.
这是在互联网上使用最合理的方式,在不使用boost/crazy模板的情况下,通过多个分隔符进行拆分。 - router

24

如果您有 boost 库,您可以使用以下代码:

#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));

21
这就像让别人打开一个泡菜罐子,而你拿出了电锯。 - Dmytro

21

使用 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};

试一试自己


3
为什么这个只有少数点赞?这绝对是太棒了!只有几行代码,不需要外部库,而且还很新颖。非常感谢! - Berkay Berabi
2
@berkayberabi 没问题 - 迟来的回答可能是原因。如果您愿意,可以发布赏金以奖励现有答案(这也会引起注意)。 - darune
嗨,我的声望不是很高。但我还有一个问题。如果我想根据括号([])作为分隔符进行拆分,我该如何传递这些括号。如果我只是输入它们,它们将被解释为另一个正则表达式,这样就行不通了。 - Berkay Berabi
1
@berkayberabi 通过 \\] 转义。 - darune
这种情况怎么处理?文本 = “Windows. Apple”;我只想看到 ['Windows' , 'Apple']。这个正则表达式会给出 [Windows ,' ', Apple],其中包含一个空格(' '),而我不想要它。 - cpchung
@cpchung 这完全取决于你的输入和目标是什么。在你的情况下,只需使用类似 [\.\s]+ 的匹配多个即可解决问题。 - darune

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

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();
}

这样,如果需要的话,您就可以利用分隔符进行一些有用的操作。


1
您可以将 std::string word; 和 char c; 移至循环内部并避免使用 clear()… 变量应尽可能为局部和短暂。 - Mohan

2
如果您想了解如何自己完成此操作而不使用boost,则以下是相关的it技术内容。
假设分隔符字符串可能非常长,比如M,对于您字符串中的每个字符检查它是否是分隔符,每次成本为O(M),因此在循环中执行所有原始字符串中的字符,长度为N,成本为O(M*N)。
我会使用字典(类似于映射 - "分隔符"到"布尔值",但这里我会使用一个简单的布尔数组,在每个分隔符的ASCII值索引处具有true)。
现在迭代字符串并检查字符是否为分隔符是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 - 返回值优化的强大功能 :)

1

多年之后,这里提供一种使用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://zh.cppreference.com/w/cpp/ranges/split_view


这是一个关于C++范围(ranges)中split_view的参考链接。

谢谢!任何想使用这个的人请检查参考链接。当前文档中的示例有点不同。 - Hitokage
6
这是不正确的。OP 询问如何使用多个分隔符,而此答案使用了单个多字符分隔符 -_-。如果您有输入 "Hello,C++;20," 并使用 std::views::split(words ",;"_sv),它将不会拆分任何内容,因为 ,; 在输入中并不存在。 - Chris

0
使用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"; });
}

这对于多个分隔符如何工作? - Daniel Schlößer
你的意思是类似于 https://godbolt.org/z/1sYzhe7zq 这样的东西吗? - Porsche9II

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