Analisi di una virgola delimitata da virgole

Se ho una stringa std :: contenente un elenco di numeri separati da virgole, qual è il modo più semplice per analizzare i numeri e inserirli in un array intero?

Non voglio generalizzare questo in analisi di qualcos’altro. Solo una semplice stringa di numeri interi separati da virgole come “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)< 

Qualcosa di meno dettagliato, std e prende qualsiasi cosa separata da una virgola.

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

Ancora un altro, piuttosto diverso, approccio: usa un locale speciale che tratta le virgole come spazio bianco:

 #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]; } }; 

Per utilizzare questo, si imbue() un stream con un locale che include questo aspetto. Dopo averlo fatto, puoi leggere i numeri come se le virgole non fossero affatto lì. Per esempio, leggeremo i numeri delimitati da virgole dall’input e scriverò quindi uno per riga sullo standard output:

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

La libreria C ++ String Toolkit Library (Strtk) presenta la seguente soluzione al tuo 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; } 

Altri esempi possono essere trovati qui

Soluzione alternativa con algoritmi generici 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()); 

Puoi anche usare la seguente funzione.

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

Sarebbe una buona idea controllare gli errori di conversione in strtol() , ovviamente. Forse il codice potrebbe beneficiare anche di altri controlli di errore.

Un sacco di risposte piuttosto terribili qui, quindi aggiungerò il mio (incluso il programma di test):

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

Belle proprietà:

  • Nessuna dipendenza (es. Boost)
  • Non una folle monotonia
  • Facile da capire (spero)
  • Gestisce perfettamente gli spazi
  • Non assegna split se non vuoi, ad esempio puoi elaborarli con una lambda come mostrato.
  • Non aggiunge caratteri uno alla volta - dovrebbe essere veloce.
  • Se si utilizza C ++ 17, è ansible cambiarlo per utilizzare uno std::stringview e quindi non eseguirà alcuna allocazione e dovrebbe essere estremamente veloce.

Alcune scelte progettuali che potresti voler cambiare:

  • Le voci vuote non vengono ignorate.
  • Una stringa vuota chiamerà f () una volta.

Esempi di input e output:

 "" -> {""} "," -> {"", ""} "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); } } 

Un input errato (ad esempio separatori consecutivi) rovinerà tutto, ma tu hai detto semplicemente.

Sono sorpreso che nessuno abbia proposto una soluzione usando std::regex ancora:

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

Questa funzione inserisce tutti gli interi nella parte posteriore del vettore di input. Puoi modificare l’espressione regolare per includere numeri interi negativi, numeri in virgola mobile, ecc.

 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]; } 

Non posso ancora commentare (per iniziare sul sito), ma ho aggiunto una versione più generica della class derivata del fantastico ctype di Jerry Coffin al suo post.

Grazie Jerry per la super idea.

(Perché deve essere sottoposto a peer-review, aggiungendolo qui anche temporaneamente)

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

struttura semplice, facilmente adattabile, facile manutenzione.

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

alla fine avrai un vettore di stringhe con ogni elemento della frase separato da spazi. le stringhe vuote vengono salvate come elementi separati.

Semplice funzione Copia / Incolla, basata sul tokenizzatore boost .

 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++; } 

Questo è il modo più semplice, che ho usato molto. Funziona con qualsiasi delimitatore di un carattere.

 #include using namespace std; int main() { string str; cin >> str; int temp; vector 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< 
 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; }