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) ); |
当然,检查
这里有很多非常糟糕的答案,所以我将添加我的(包括测试程序):
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",""} " ,," -> {"","",""} |
我很惊讶还没有人提出使用
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; } |