het parseren van een komma-gescheiden std :: string

Als ik een STD ::-string met een door komma’s gescheiden lijst met nummers bevat, wat is de eenvoudigste manier om de cijfers uit te pareren en deze in een getal-array te plaatsen?

Ik wil dit niet generaliseren in het parseren van iets anders. Gewoon een eenvoudige reeks door komma’s gescheiden geheel getallen zoals “1,11,1,2,1,1,1 7.


Antwoord 1, Autoriteit 100%

Voer één nummer per keer in en controleer of het volgende teken ,is. Als dit het geval is, gooi het weg.

#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);
    for (int i; ss >> i;) {
        vect.push_back(i);    
        if (ss.peek() == ',')
            ss.ignore();
    }
    for (std::size_t i = 0; i < vect.size(); i++)
        std::cout << vect[i] << std::endl;
}

Antwoord 2, Autoriteit 90%

iets minder uitgebreid, std en neemt alles gescheiden door een komma.

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

Antwoord 3, Autoriteit 41%

Nog een andere, nogal anders, benadering: gebruik een speciale locale die komma’s behandelt als witte ruimte:

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

Om dit te gebruiken, imbue()een stream met een landinstelling die dit facet bevat. Als je dat eenmaal hebt gedaan, kun je cijfers lezen alsof de komma’s er helemaal niet zijn. We lezen bijvoorbeeld door komma’s gescheiden getallen van invoer en schrijven er één per regel uit op standaarduitvoer:

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

Antwoord 4, autoriteit 27%

De C++ String Toolkit Library (Strtk)heeft de volgende oplossing voor uw probleem:

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

Meer voorbeelden vindt u Hier


Antwoord 5, autoriteit 11%

Alternatieve oplossing met generieke algoritmen en 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());

Antwoord 6, autoriteit 4%

U kunt ook de volgende functie gebruiken.

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

Antwoord 7, autoriteit 4%

Veel behoorlijk vreselijke antwoorden hier, dus ik zal de mijne toevoegen (inclusief testprogramma):

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

Mooie eigendommen:

  • Geen afhankelijkheden (bijv. boost)
  • Geen gekke oneliner
  • Gemakkelijk te begrijpen (hoop ik)
  • Gaat prima om met spaties
  • Wijst geen splitsingen toe als u dat niet wilt, b.v. je kunt ze verwerken met een lambda zoals afgebeeld.
  • Voegt tekens niet één voor één toe – zou snel moeten zijn.
  • Als je C++17 gebruikt, zou je het kunnen veranderen om een std::stringviewte gebruiken en dan zal het geen toewijzingen doen en zou het extreem snel moeten zijn.

Sommige ontwerpkeuzes die u mogelijk wilt wijzigen:

  • Lege vermeldingen worden niet genegeerd.
  • Een lege string roept f() één keer aan.

Voorbeeld in- en uitgangen:

""      ->   {""}
","     ->   {"", ""}
"1,"    ->   {"1", ""}
"1"     ->   {"1"}
" "     ->   {" "}
"1, 2," ->   {"1", " 2", ""}
" ,, "  ->   {" ", "", " "}

Antwoord 8, autoriteit 2%

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

Het zou natuurlijk een goed idee zijn om te controleren op conversiefouten in strtol(). Misschien kan de code ook profiteren van enkele andere foutcontroles.


Antwoord 9, autoriteit 2%

Het verbaast me dat niemand een oplossing heeft voorgesteld met behulp van std::regexnog:

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

Deze functie voegt alle gehele getallen achter in de invoervector in. U kunt de reguliere expressie aanpassen om negatieve gehele getallen of getallen met drijvende komma op te nemen, enz.


Antwoord 10

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

Slechte invoer (bijvoorbeeld opeenvolgende scheidingstekens) zal dit verpesten, maar je zei wel simpel.


Antwoord 11

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

Antwoord 12

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

Antwoord 13

Ik kan nog niet op reageren (aan de slag op de site) maar een generieke versie van de afgeleide klasse van Cypte van Jerry Coffin aan zijn post toegevoegd.

Bedankt Jerry voor het Super Idea.

(Omdat het peer-reviewed moet zijn, het hier al te tijdelijk toevoegen)

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

Antwoord 14

Dit is de eenvoudigste manier, die ik veel heb gebruikt. Het werkt voor een scheidingsteken van één personages.

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

Antwoord 15

eenvoudige structuur, gemakkelijk aanpasbaar, eenvoudig onderhoud.

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

uiteindelijk heb je een vector van strings met elk element in de zin gescheiden door spaties. lege strings worden opgeslagen als aparte items.


Antwoord 16

Eenvoudige functie kopiëren/plakken, gebaseerd op de 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++;
}

Antwoord 17

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

Antwoord 18

#include <sstream>
#include <vector>
#include <algorithm>
#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;
}

Other episodes