Analisando um std :: string delimitado por vírgula

Se eu tiver uma string std :: contendo uma lista de números separados por vírgula, qual é a maneira mais simples de analisar os números e colocá-los em uma matriz de inteiros?

Eu não quero generalizar isso em analisar qualquer outra coisa. Apenas uma sequência simples de números inteiros separados por vírgula, como “1,1,1,1,2,1,1,1,0”.

#include  #include  #include  #include  int main() { std::string str = "1,2,3,4,5,6"; std::vector 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)< 

Algo menos detalhado, std e leva qualquer coisa separada por uma vírgula.

 stringstream ss( "1,1,1,1, or something else ,1,1,1,0" ); vector result; while( ss.good() ) { string substr; getline( ss, substr, ',' ); result.push_back( substr ); } 

Ainda outra abordagem bastante diferente: use uma localidade especial que trata vírgulas como espaço em branco:

 #include  #include  struct csv_reader: std::ctype { csv_reader(): std::ctype(get_table()) {} static std::ctype_base::mask const* get_table() { static std::vector 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]; } }; 

Para usar isso, você imbue() um stream com uma localidade que inclua essa faceta. Depois de fazer isso, você pode ler números como se as vírgulas não estivessem presentes. Por exemplo, vamos ler números separados por vírgula da input e escrever um por linha na saída padrão:

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

A biblioteca do C ++ String Toolkit (Strtk) tem a seguinte solução para o seu problema:

 #include  #include  #include  #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_list; strtk::parse(int_string,",",int_list); std::string double_string = "123.456|789.012|345.678|901.234|567.890"; std::deque double_list; strtk::parse(double_string,"|",double_list); return 0; } 

Mais exemplos podem ser encontrados aqui

Solução alternativa usando algoritmos genéricos e 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 ints; tokenizer<> tok(values); transform(tok.begin(), tok.end(), back_inserter(ints), ToInt()); 

Você também pode usar a seguinte function.

 void tokenize(const string& str, vector& 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); } } 
 std::string input="1,1,1,1,2,1,1,1,0"; std::vector 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) ); 

Seria uma boa ideia verificar se há erros de conversão em strtol() , é claro. Talvez o código também possa se beneficiar de outras verificações de erros.

Muitas respostas terríveis aqui, então adicionarei as minhas (incluindo o programa de teste):

 #include  #include  #include  template 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; } 

Propriedades agradáveis:

  • Nenhuma dependência (por exemplo, impulso)
  • Não é um one-liner insano
  • Fácil de entender (espero)
  • Lida com espaços perfeitamente bem
  • Não aloca divisões se você não quiser, por exemplo, você pode processá-las com um lambda como mostrado.
  • Não adiciona caracteres um de cada vez - deve ser rápido.
  • Se estiver usando o C ++ 17, você poderá alterá-lo para usar um std::stringview e, em seguida, ele não fará nenhuma alocação e deverá ser extremamente rápido.

Algumas escolhas de design que você pode querer mudar:

  • Entradas vazias não são ignoradas.
  • Uma string vazia irá chamar f () uma vez.

Exemplo de inputs e saídas:

 "" -> {""} "," -> {"", ""} "1," -> {"1", ""} "1" -> {"1"} " " -> {" "} "1, 2," -> {"1", " 2", ""} " ,, " -> {" ", "", " "} 
 #include  #include  const char *input = "1,1,1,1,2,1,1,1,0"; int main() { std::stringstream ss(input); std::vector output; int i; while (ss >> i) { output.push_back(i); ss.ignore(1); } } 

Entrada incorreta (por exemplo, separadores consecutivos) vai atrapalhar isso, mas você disse simples.

Estou surpreso que ninguém tenha proposto uma solução usando o std::regex ainda:

 #include  #include  #include  #include  void parse_csint( const std::string& str, std::vector& result ) { typedef std::regex_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]); } ); } 

Esta function insere todos os inteiros na parte de trás do vetor de input. Você pode ajustar a expressão regular para include inteiros negativos ou números de ponto flutuante, etc.

 string exp = "token1 token2 token3"; char delimiter = ' '; vector str; string acc = ""; for(int i = 0; i < exp.size(); i++) { if(exp[i] == delimiter) { str.push_back(acc); acc = ""; } else acc += exp[i]; } 

Eu ainda não posso comentar (começando no site) mas adicionei uma versão mais genérica da class derivada do ctype fantástico de Jerry Coffin ao seu post.

Obrigado Jerry pela super ideia.

(Porque deve ser revisado por colegas, adicionando-o aqui temporariamente)

 struct SeparatorReader: std::ctype { template SeparatorReader(const T &seps): std::ctype(get_table(seps), true) {} template std::ctype_base::mask const *get_table(const T &seps) { auto &&rc = new std::ctype_base::mask[std::ctype::table_size](); for(auto &&sep: seps) rc[static_cast(sep)] = std::ctype_base::space; return &rc[0]; } }; 
 bool GetList (const std::string& src, std::vector& res) { using boost::lexical_cast; using boost::bad_lexical_cast; bool success = true; typedef boost::tokenizer > tokenizer; boost::char_separator sepa(","); tokenizer tokens(src, sepa); for (tokenizer::iterator tok_iter = tokens.begin(); tok_iter != tokens.end(); ++tok_iter) { try { res.push_back(lexical_cast(*tok_iter)); } catch (bad_lexical_cast &) { success = false; } } return success; } 

estrutura simples, facilmente adaptável, fácil manutenção.

 std::string stringIn = "my,csv,,is 10233478,separated,by commas"; std::vector commaSeparated(1); int commaCounter = 0; for (int i=0; i 

no final, você terá um vetor de strings com todos os elementos da frase separados por espaços. seqüências vazias são salvas como itens separados.

Função simples Copiar / Colar, baseada no tokenizador de reforço .

 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++; } 
 void ExplodeString( const std::string& string, const char separator, std::list& 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) ); } } 
 #include  #include  #include  #include  const char *input = ",,29870,1,abc,2,1,1,1,0"; int main() { std::stringstream ss(input); std::vector 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 (std::cout, " ") ); return 0; }