关于C++:解析逗号分隔的STD::字符串

Parsing a comma-delimited std::string

本问题已经有最佳答案,请猛点这里访问。

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

我不想把这概括为解析任何其他内容。只是一个简单的逗号分隔整数字符串,如"1,1,1,1,2,1,1,1,1,0"。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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);

    int i;

    while (ss >> i)
    {
        vect.push_back(i);

        if (ss.peek() == ',')
            ss.ignore();
    }

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


一些不那么冗长的东西,std,用逗号分隔。

1
2
3
4
5
6
7
8
9
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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['
'
] = std::ctype_base::space;
        rc[' '] = std::ctype_base::space;
        return &rc[0];
    }
};

要使用这个,您需要使用一个包含这个方面的区域设置的流。一旦你做到了这一点,你就可以读数字,就好像逗号根本不在那里一样。例如,我们将从输入中读取逗号分隔的数字,然后在标准输出中每行写出一个:

1
2
3
4
5
6
7
8
9
10
11
12
#include
#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,"
"
));
    return 0;
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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;
}

这里有更多的例子


使用通用算法和Boost的替代解决方案。标记器:

1
2
3
4
5
6
7
8
9
10
11
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());


您还可以使用以下功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
  }
}


1
2
3
4
5
6
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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) <<"`
"
;
    });

    return 0;
}

优良的性能:

  • 无依赖性(例如Boost)
  • 不是疯狂的一行
  • 容易理解(我希望)
  • 处理空间非常好
  • 如果不想分配拆分,则不分配拆分,例如,可以使用lambda处理拆分,如图所示。
  • 一次不添加一个字符-应该很快。
  • 如果使用C++ 17,你可以改变它使用EDCOX1,3,然后它不会做任何分配,而且应该非常快。

您可能希望更改的某些设计选项:

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

输入和输出示例:

1
2
3
4
5
6
7
""      ->   {""}
","     ->   {"",""}
"1,"    ->   {"1",""}
"1"     ->   {"1"}
""     ->   {""}
"1, 2," ->   {"1"," 2",""}
" ,,"  ->   {"","",""}

我很惊讶还没有人提出使用std::regex的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string>
#include
#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]); } );

}

此函数在输入向量的后面插入所有整数。您可以调整正则表达式,使其包含负整数或浮点数等。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sstream>
#include <vector>

const char *input ="1,1,1,1,2,1,1,1,0";

int main() {
    std::stringstream ss(input);
    std::vector<int> output;
    int i;
    while (ss >> i) {
        output.push_back(i);
        ss.ignore(1);
    }
}

错误的输入(例如连续分隔符)会把这搞得一团糟,但您确实说过简单。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
string exp ="token1 token2 token3";
char delimiter = ' ';
vector<string> str;
string acc ="";
for(int i = 0; i < exp.size(); i++)
{
    if(exp[i] == delimiter)
    {
        str.push_back(acc);
        acc ="";
    }
    else
        acc += exp[i];
}


我还不能发表评论(在网站上开始),但在他的帖子中添加了一个更通用版本的杰里·科芬的神奇CType的派生类。

谢谢杰瑞的绝妙创意。

(因为它必须经过同行评审,所以在此处临时添加)

1
2
3
4
5
6
7
8
9
10
11
12
13
struct SeparatorReader: std::ctype<char>
{
    template<typename T>
    SeparatorReader(const T &seps): std::ctype<char>(get_table(seps), true) {}

    template<typename T>
    std::ctype_base::mask const *get_table(const T &seps) {
        auto &&rc = new std::ctype_base::mask[std::ctype<char>::table_size]();
        for(auto &&sep: seps)
            rc[static_cast<unsigned char>(sep)] = std::ctype_base::space;
        return &rc[0];
    }
};

基于Boost标记器的简单复制/粘贴功能。

1
2
3
4
5
6
7
8
void strToIntArray(std::string string, int* array, int array_len) {
  boost::tokenizer<> tok(string);
  int i = 0;
  for(boost::tokenizer<>::iterator beg=tok.begin(); beg!=tok.end();++beg){
    if(i < array_len)
      array[i] = atoi(beg->c_str());
    i++;
}

结构简单,适应性强,维修方便。

1
2
3
4
5
6
7
8
9
10
11
std::string stringIn ="my,csv,,is 10233478,separated,by commas";
std::vector<std::string> commaSeparated(1);
int commaCounter = 0;
for (int i=0; i<stringIn.size(); i++) {
    if (stringIn[i] ==",") {
        commaSeparated.push_back("");
        commaCounter++;
    } else {
        commaSeparated.at(commaCounter) += stringIn[i];
    }
}

最后,您将得到一个字符串的向量,句子中的每个元素都由空格分隔。空字符串保存为单独的项。


这是最简单的方法,我用了很多。它适用于任何一个字符分隔符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;

int main() {
   string str;

   cin >> str;
   int temp;
   vector<int> result;
   char ch;
   stringstream ss(str);

   do
   {
       ss>>temp;
       result.push_back(temp);
   }while(ss>>ch);

   for(int i=0 ; i < result.size() ; i++)
       cout<<result[i]<<endl;

   return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool GetList (const std::string& src, std::vector<int>& res)
  {
    using boost::lexical_cast;
    using boost::bad_lexical_cast;
    bool success = true;
    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
    boost::char_separator<char> sepa(",");
    tokenizer tokens(src, sepa);
    for (tokenizer::iterator tok_iter = tokens.begin();
         tok_iter != tokens.end(); ++tok_iter) {
      try {
        res.push_back(lexical_cast<int>(*tok_iter));
      }
      catch (bad_lexical_cast &) {
        success = false;
      }
    }
    return success;
  }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ExplodeString( const std::string& string, const char separator, std::list<int>& result ) {
    if( string.size() ) {
        std::string::const_iterator last = string.begin();
        for( std::string::const_iterator i=string.begin(); i!=string.end(); ++i ) {
            if( *i == separator ) {
                const std::string str(last,i);
                int id = atoi(str.c_str());
                result.push_back(id);
                last = i;
                ++ last;
            }
        }
        if( last != string.end() ) result.push_back( atoi(&*last) );
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <sstream>
#include <vector>
#include
#include <iterator>

const char *input =",,29870,1,abc,2,1,1,1,0";
int main()
{
    std::stringstream ss(input);
    std::vector<int> output;
    int i;
    while ( !ss.eof() )
    {
       int c =  ss.peek() ;
       if ( c < '0' || c > '9' )
       {
          ss.ignore(1);
          continue;
        }

       if (ss >> i)
       {
          output.push_back(i);
        }

    }

    std::copy(output.begin(), output.end(), std::ostream_iterator<int> (std::cout,"") );
    return 0;
}