解析逗号分隔的std :: string

164

如果我有一个包含逗号分隔数字的std::string,最简单的方法是什么来解析这些数字并将它们放入整数数组中?

我不想将其泛化到解析其他任何内容。只是一个简单的逗号分隔整数数字字符串,如"1,1,1,1,2,1,1,1,0"。


2
对于任何需要解析逗号分隔字符串的人 https://dev59.com/CGgt5IYBdhLWcg3w7BoE - Sam B
18个回答

186

每次输入一个数字,并检查下一个字符是否为,。如果是,则忽略它。

#include <vector>
#include <string>
#include <sstream>
#include <iostream>

int main()
{
    std::string str = "1,2,3,4,5,6";
    std::vector<int> vect;

    std::stringstream ss(str);

    for (int i; ss >> i;) {
        vect.push_back(i);    
        if (ss.peek() == ',')
            ss.ignore();
    }

    for (std::size_t i = 0; i < vect.size(); i++)
        std::cout << vect[i] << std::endl;
}

14
如果在逗号之前有空格,我认为这会失败。 - KeithB
41
可以,但是空格不是最初的问题之一。 - user229321
7
覆盖那部分内容:if (ss.peek() == ',' || ss.peek() == ' '),意思是如果ss流的下一个字符是逗号或空格。 - safe_malloc
我对C++还比较新。在这种情况下,使用size_t i;而不是int i;会更好吗?因为vect.size()返回的是size_t变量类型。 - Volomike
8
@safe_malloc: 这应该是 while (ss.peek() == ',' || ss.peek() == ' '),而不是原文中的内容。 - saurabheights
显示剩余5条评论

173

更简洁,使用标准且适用于逗号分隔的任何内容。

stringstream ss( "1,1,1,1, or something else ,1,1,1,0" );
vector<string> result;

while( ss.good() )
{
    string substr;
    getline( ss, substr, ',' );
    result.push_back( substr );
}

1
这对我非常有效。代码行数很少,而且效果非常好。 - mauricioSanchez
4
易于阅读,并且适应空格很好。谢谢! - sudo make install
1
简单易读,不需要任何特殊库! - Johan Engblom
请注意,对于空字符串,这仍然会执行 result.push_back("");,这可能不是您想要的。 - Timmmm
1
它是否将逗号后的空格视为字符串的一部分?即在引号内或其他地方的空格之前或之后? - Rajesh
有没有一种方法可以在“,”后丢弃空格?空格被存储为字符串的一部分。 - Miraz

70

另一种截然不同的方法:使用将逗号视为空格的特殊语言环境:

#include <locale>
#include <vector>

struct csv_reader: std::ctype<char> {
    csv_reader(): std::ctype<char>(get_table()) {}
    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> rc(table_size, std::ctype_base::mask());

        rc[','] = std::ctype_base::space;
        rc['\n'] = std::ctype_base::space;
        rc[' '] = std::ctype_base::space;
        return &rc[0];
    }
}; 

要使用此功能,您需要用包含此 facet 的 locale 对流进行 imbue()。完成后,您可以像逗号不存在一样读取数字。为了举例说明,我们将从输入中读取逗号分隔的数字,并将它们一个接一个地写入标准输出:

#include <algorithm>
#include <iterator>
#include <iostream>

int main() {
    std::cin.imbue(std::locale(std::locale(), new csv_reader()));
    std::copy(std::istream_iterator<int>(std::cin), 
              std::istream_iterator<int>(),
              std::ostream_iterator<int>(std::cout, "\n"));
    return 0;
}

1
我见过的最有创意的答案! - yoco
1
这是一个工作示例,如果有人想尝试,请看:http://ideone.com/RX5o10 - kravemir
1
请注意,如果输入看起来像“1,2,3,4,5...”的话,上面的示例会出错,您需要添加一行代码 rc[' '] = ctype_base::space;。我花了一些时间才明白这一点。 - aCuria
你可以在这里看到真正的力量:string tmp = "1,2,blab,blub"; int startTime2 = 0; int endTime2 = 0; string startDate2; string endDate2; stringstream ss( tmp ); ss.imbue( std::locale( std::locale(), new csv_reader() ) ); ss >> startTime2 >> endTime2 >> startDate2 >> endDate2; - tmanthey
2
很抱歉,这个解决方案不支持空字符串,它们将被跳过。例如,输入为:1,2,3,,5,6,7 - Fabio A.
显示剩余2条评论

45

C++字符串工具库(Strtk) 对你的问题有以下解决方案:

#include <string>
#include <deque>
#include <vector>
#include "strtk.hpp"
int main()
{ 
   std::string int_string = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15";
   std::vector<int> int_list;
   strtk::parse(int_string,",",int_list);

   std::string double_string = "123.456|789.012|345.678|901.234|567.890";
   std::deque<double> double_list;
   strtk::parse(double_string,"|",double_list);

   return 0;
}

更多示例可以在这里找到


这怎么可能是一个解决方案呢?? sample.cpp(104): fatal error C1083: 无法打开包括文件: 'strtk.hpp': 没有那个文件或目录 - Sam B

20

使用通用算法和Boost.Tokenizer的另一种解决方案:

struct ToInt
{
    int operator()(string const &str) { return atoi(str.c_str()); }
};

string values = "1,2,3,4,5,9,8,7,6";

vector<int> ints;
tokenizer<> tok(values);

transform(tok.begin(), tok.end(), back_inserter(ints), ToInt());

14
如果您正在使用Boost.Tokenizer,为什么不用boost::lexical_cast取代atoi呢? - Andriy Tylychko

10

这里有许多相当糟糕的回答,所以我会附上我的(包括测试程序):

#include <string>
#include <iostream>
#include <cstddef>

template<typename StringFunction>
void splitString(const std::string &str, char delimiter, StringFunction f) {
  std::size_t from = 0;
  for (std::size_t i = 0; i < str.size(); ++i) {
    if (str[i] == delimiter) {
      f(str, from, i);
      from = i + 1;
    }
  }
  if (from <= str.size())
    f(str, from, str.size());
}


int main(int argc, char* argv[]) {
    if (argc != 2)
        return 1;

    splitString(argv[1], ',', [](const std::string &s, std::size_t from, std::size_t to) {
        std::cout << "`" << s.substr(from, to - from) << "`\n";
    });

    return 0;
}

优点:

  • 没有任何依赖(例如boost)
  • 不是一个疯狂的一行代码
  • 易于理解(我希望如此)
  • 可以很好地处理空格
  • 如果你不想要分割,就不会分配它们,例如你可以用lambda函数来处理它们。
  • 不是逐个添加字符 - 应该很快。
  • 如果使用C++17,你可以将其更改为使用std::stringview,然后它将不会做任何分配,并且应该非常快。

一些您可能希望更改的设计选择:

  • 空条目不被忽略。
  • 空字符串将调用f()一次。

示例输入和输出:

""      ->   {""}
","     ->   {"", ""}
"1,"    ->   {"1", ""}
"1"     ->   {"1"}
" "     ->   {" "}
"1, 2," ->   {"1", " 2", ""}
" ,, "  ->   {" ", "", " "}

8
您也可以使用以下功能。
void tokenize(const string& str, vector<string>& tokens, const string& delimiters = ",")
{
  // Skip delimiters at beginning.
  string::size_type lastPos = str.find_first_not_of(delimiters, 0);

  // Find first non-delimiter.
  string::size_type pos = str.find_first_of(delimiters, lastPos);

  while (string::npos != pos || string::npos != lastPos) {
    // Found a token, add it to the vector.
    tokens.push_back(str.substr(lastPos, pos - lastPos));

    // Skip delimiters.
    lastPos = str.find_first_not_of(delimiters, pos);

    // Find next non-delimiter.
    pos = str.find_first_of(delimiters, lastPos);
  }
}

4
std::string input="1,1,1,1,2,1,1,1,0";
std::vector<long> output;
for(std::string::size_type p0=0,p1=input.find(',');
        p1!=std::string::npos || p0!=std::string::npos;
        (p0=(p1==std::string::npos)?p1:++p1),p1=input.find(',',p0) )
    output.push_back( strtol(input.c_str()+p0,NULL,0) );

当然,在strtol()中检查转换错误是一个好主意。也许这段代码还可以从其他错误检查中受益。


1
真是一团糟!你不必把所有东西都放在一行上。 - Timmmm
@Timmmm,你不必告诉我我不必做什么,你也不被强制使用这段代码,对吧? - Michael Krelin - hacker
抱歉,我并不是有意冒犯 - 只是建议您可以将代码分解一下,这样可以更容易理解,并且也能减少漏洞的出现。虽然我没有被强制使用它,但这并不意味着我不能表达自己的观点。这就是 SO 投票系统的全部意义。 - Timmmm
1
@Timmmm,当然可以,我完全可以接受你的意见与我的不同。个人而言,我确实认为压缩代码有价值,因为它更易于阅读,尽管在每单位时间内的行数方面较慢。我也知道这是我的观点,其他人可能看法不同。我真的认为他们的代码很混乱,但我会克制不说出来 :) - Michael Krelin - hacker

4

我很惊讶还没有人提出使用 std::regex 来解决问题:

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

void parse_csint( const std::string& str, std::vector<int>& result ) {

    typedef std::regex_iterator<std::string::const_iterator> re_iterator;
    typedef re_iterator::value_type re_iterated;

    std::regex re("(\\d+)");

    re_iterator rit( str.begin(), str.end(), re );
    re_iterator rend;

    std::transform( rit, rend, std::back_inserter(result), 
        []( const re_iterated& it ){ return std::stoi(it[1]); } );

}

这个函数会将所有整数插入输入向量的末尾。您可以调整正则表达式以包括负整数、浮点数等。

3
std::string exp = "token1 token2 token3";
char delimiter = ' ';
std::vector<std::string> str;
std::string acc = "";
for(const auto &x : exp)
{
    if(x == delimiter)
    {
        str.push_back(acc);
        acc = "";
    }
    else
        acc += x;
}
str.push_back(acc);

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