web-dev-qa-db-fra.com

Analyser un std :: string délimité par des virgules

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".

110
Piku
#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;
}
131
user229321

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

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;
}
59
Jerry Coffin

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

43
Matthieu N.

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());
17
TC.

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);
  }
}
6
kiamlaluno
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.

5

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:

  • Aucune dépendance (par exemple boost)
  • Pas un one-liner fou
  • Facile à comprendre (j'espère)
  • Gère parfaitement les espaces
  • N'alloue pas de fractionnements si vous ne le souhaitez pas, par exemple vous pouvez les traiter avec un lambda comme indiqué.
  • N'ajoute pas de caractères un par un - devrait être rapide.
  • Si vous utilisez C++ 17, vous pouvez le changer pour utiliser un 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:

  • Les entrées vides ne sont pas ignorées.
  • Une chaîne vide appellera f() une fois.

Exemple d'entrées et de sorties:

""      ->   {""}
","     ->   {"", ""}
"1,"    ->   {"1", ""}
"1"     ->   {"1"}
" "     ->   {" "}
"1, 2," ->   {"1", " 2", ""}
" ,, "  ->   {" ", "", " "}
4
Timmmm
#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.

2
Steve Jessop

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.

2
Sheljohn

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];
    }
};
1
mementum
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];
}
1
knapcio

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++;
}
0
Daniel Eckert
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;
  }
0
KeithB

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.

0
tony gil

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;
}
0
Akhilesh Agrahari