Si j'ai un std :: string contenant une liste de nombres séparés par des virgules, quel est le moyen le plus simple d'analyser les nombres et de les placer dans un tableau entier?
Je ne veux pas généraliser ceci en analysant autre chose. Juste une simple chaîne de nombres entiers séparés par des virgules, tels que "1,1,1,1,2,1,1,1,0".
#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;
}
Quelque chose de moins verbeux, std et prend tout ce qui est séparé par une virgule.
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 );
}
Une autre approche, assez différente, consiste à utiliser des paramètres régionaux spéciaux qui traitent les virgules comme des espaces:
#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];
}
};
Pour utiliser cela, vous imbue()
un flux avec une locale qui inclut cette facette. Une fois que vous avez fait cela, vous pouvez lire les chiffres comme si les virgules n'étaient pas là. Par exemple, nous lirons des nombres séparés par des virgules à partir de l’entrée et nous écrirons une ligne par ligne sur la sortie standard:
#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;
}
La bibliothèque de boîtes à outils C++ String (Strtk) offre la solution suivante à votre problème:
#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;
}
Plus d'exemples peuvent être trouvés Ici
Solution alternative utilisant des algorithmes génériques et 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());
Vous pouvez également utiliser la fonction suivante.
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);
}
}
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) );
Ce serait une bonne idée de vérifier les erreurs de conversion dans strtol()
, bien sûr. Peut-être que le code pourrait également bénéficier d'autres vérifications d'erreur.
Beaucoup de réponses assez terribles ici donc je vais ajouter le mien (y compris le programme de test):
#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;
}
Belles propriétés:
std::stringview
. Dans ce cas, il ne fera aucune allocation et devrait être extrêmement rapide.Quelques choix de conception que vous voudrez peut-être changer:
Exemple d'entrées et de sorties:
"" -> {""}
"," -> {"", ""}
"1," -> {"1", ""}
"1" -> {"1"}
" " -> {" "}
"1, 2," -> {"1", " 2", ""}
" ,, " -> {" ", "", " "}
#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);
}
}
Une mauvaise entrée (par exemple des séparateurs consécutifs) va gâcher cela, mais vous avez dit simple.
Je suis surpris que personne n'ait proposé de solution utilisant std::regex
encore:
#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]); } );
}
Cette fonction insère tous les entiers à l’arrière du vecteur d’entrée. Vous pouvez modifier l'expression régulière pour inclure des nombres entiers négatifs, des nombres à virgule flottante, etc.
Je ne peux pas encore commenter (commencer sur le site), mais j'ai ajouté à sa publication une version plus générique de la classe dérivée du fantastique ctype de Jerry Coffin.
Merci Jerry pour cette super idée.
(Parce qu'il doit être évalué par les pairs, l'ajouter ici aussi temporairement)
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];
}
};
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];
}
Fonction Copier/Coller simple, basée sur le boost tokenizer .
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++;
}
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;
}
structure simple, facilement adaptable, entretien facile.
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];
}
}
à la fin, vous aurez un vecteur de chaînes avec chaque élément de la phrase séparé par des espaces. les chaînes vides sont enregistrées en tant qu'éléments séparés.
C'est le moyen le plus simple, que j'ai beaucoup utilisé. Cela fonctionne pour tout délimiteur à un caractère.
#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;
}