Come posso tokenizzare una stringa in C ++?


414

Java ha un comodo metodo split:

String str = "The quick brown fox";
String[] results = str.split(" ");

C'è un modo semplice per farlo in C ++?


172
Non riesco a credere che questo compito di routine sia un tale mal di testa in c ++
wfbarksdale,

6
Non è mal di testa in c ++ - ci sono vari modi per raggiungerlo. i programmatori sono meno consapevoli di c ++ di c # - si tratta di marketing e investimenti ... vedere questo per varie opzioni di c ++ per ottenere lo stesso risultato: cplusplus.com/faq/sequences/strings/split
hB0

9
@ hB0 passare attraverso molte risposte alle domande e non decidere ancora significa che è un mal di testa. l'uno ha bisogno di quella biblioteca, l'altro è solo per gli spazi, l'altro non gestisce gli spazi ..
Paschalis

1
Possibile duplicato di dividere una stringa in C ++?
KOB

2
Perché tutto in C ++ deve essere una lotta?
Wael Assaf,

Risposte:


145

Gli algoritmi di libreria standard C ++ si basano piuttosto universalmente su iteratori piuttosto che su contenitori concreti. Sfortunatamente ciò rende difficile fornire una splitfunzione simile a Java nella libreria standard C ++, anche se nessuno sostiene che sarebbe conveniente. Ma quale sarebbe il suo tipo di ritorno?std::vector<std::basic_string<…>>? Forse, ma poi siamo costretti a eseguire allocazioni (potenzialmente ridondanti e costose).

Al contrario, C ++ offre una moltitudine di modi per dividere le stringhe in base a delimitatori arbitrariamente complessi, ma nessuno di essi è incapsulato in modo altrettanto preciso che in altre lingue. I numerosi modi riempiono interi post del blog .

Nella sua forma più semplice, puoi iterare usando std::string::findfino a quando non colpisci std::string::nposed estrarre i contenuti usando std::string::substr.

Una versione più fluida (e idiomatica, ma di base) per la divisione su spazi bianchi userebbe un std::istringstream:

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

Usando std::istream_iterators , il contenuto del flusso di stringhe può anche essere copiato in un vettore usando il suo costruttore di intervalli iteratore.

Più librerie (come Boost.Tokenizer ) offrono tokeniser specifici.

Una divisione più avanzata richiede espressioni regolari. C ++ fornisce std::regex_token_iteratora questo scopo in particolare:

auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
    std::sregex_token_iterator{begin(str), end(str), re, -1},
    std::sregex_token_iterator{}
);

53
Purtroppo, boost non è sempre disponibile per tutti i progetti. Dovrò cercare una risposta non boost.
FuzzyBunnySlippers il

36
Non tutti i progetti sono aperti a "open source". Lavoro in settori fortemente regolamentati. Non è un problema, davvero. È solo un dato di fatto. Boost non è disponibile ovunque.
FuzzyBunnySlippers il

5
@NonlinearIdeas L'altra domanda / risposta non riguardava affatto i progetti Open Source. Lo stesso vale per qualsiasi progetto. Detto questo, ovviamente capisco gli standard limitati come MISRA C, ma poi si capisce che si costruisce comunque tutto da zero (a meno che non si trovi una libreria conforme, una rarità). Ad ogni modo, il punto non è certo che "Boost non è disponibile" - è che hai requisiti speciali per i quali quasi tutte le risposte di carattere generale non sarebbero adatte.
Konrad Rudolph,

1
@NonlinearIdeas Caso in questione, anche le altre risposte non Boost non sono conformi MISRA.
Konrad Rudolph,

3
@Dmitry Che cos'è "STL barf" ?! E l'intera comunità è molto favorevole alla sostituzione del preprocessore C - in effetti, ci sono proposte per farlo. Ma il tuo suggerimento di usare PHP o qualche altra lingua sarebbe invece un enorme passo indietro.
Konrad Rudolph,

188

La classe di tokenizer Boost può rendere questo genere di cose abbastanza semplice:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

Aggiornato per C ++ 11:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) {
        cout << t << "." << endl;
    }
}

1
Roba buona, l'ho utilizzata di recente. Il mio compilatore di Visual Studio ha un whinge dispari fino a quando non utilizzo uno spazio bianco per separare i due caratteri ">" prima dei bit dei token (testo, sep): (errore C2947: aspettarsi '>' per terminare l'elenco argomenti-template, trovato '> > ')
AndyUK

@AndyUK sì, senza lo spazio il compilatore lo analizza come un operatore di estrazione anziché due modelli di chiusura.
EnabrenTane,

Teoricamente è stato corretto in C ++ 0x
David Souther,

3
attenzione ai terzi parametri del char_separatorcostruttore ( drop_empty_tokensè l'impostazione predefinita, l'alternativa è keep_empty_tokens).
Benoit,

5
@puk - È un suffisso comunemente usato per i file di intestazione C ++. (come .hper le intestazioni C)
Ferruccio,

167

Eccone uno molto semplice:

#include <vector>
#include <string>
using namespace std;

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}

devo aggiungere un prototipo per questo metodo nel file .h?
Suhrob Samiev,

5
Questa non è esattamente la risposta "migliore" in quanto utilizza ancora una stringa letterale che è la semplice matrice di caratteri costanti C. Credo che l'interrogatore chiedesse se poteva tokenizzare una stringa C ++ che è di tipo "stringa" introdotta da quest'ultima.
Vijay Kumar Kanta,

Ciò ha bisogno di una nuova risposta perché sospetto fortemente che l'inclusione di espressioni regolari in C ++ 11 abbia cambiato la risposta migliore.
Onnipotente il

113

Usa strtok. A mio avviso, non è necessario creare una classe sul tokenizzazione a meno che strtok non ti fornisca ciò di cui hai bisogno. Potrebbe non esserlo, ma in oltre 15 anni di scrittura di vari codici di analisi in C e C ++, ho sempre usato strtok. Ecco un esempio

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

Alcune avvertenze (che potrebbero non soddisfare le tue esigenze). La stringa viene "distrutta" nel processo, il che significa che i caratteri EOS vengono posizionati in linea nei punti del delimitatore. L'uso corretto potrebbe richiedere di creare una versione non const della stringa. È inoltre possibile modificare l'elenco dei delimitatori durante l'analisi.

A mio avviso, il codice sopra è molto più semplice e facile da usare rispetto alla scrittura di una classe separata per esso. Per me, questa è una di quelle funzioni che la lingua fornisce e lo fa bene e in modo pulito. È semplicemente una soluzione a "C". È appropriato, è facile e non devi scrivere molto codice extra :-)


42
Non che non mi piaccia C, tuttavia strtok non è thread-safe, e devi essere certo che la stringa che invii contiene un carattere null per evitare un possibile overflow del buffer.
contattare

11
C'è strtok_r, ma questa era una domanda C ++.
contratto del Prof. Falken ha violato il

3
@tloach: nel compilatore MS C ++ strtok è thread-safe in quanto la variabile statica interna viene creata sul TLS (archiviazione locale thread) (in realtà dipende dal compilatore)
Ahmed Said

3
@ahmed: thread safe significa molto di più che poter eseguire due volte la funzione in thread diversi. In questo caso, se il thread viene modificato mentre strtok è in esecuzione, è possibile che la stringa sia valida durante l'intera esecuzione di strtok, ma strtok continuerà a incasinare perché la stringa è cambiata, ora è già oltre il carattere null e sta per continua a leggere la memoria fino a quando non riceve una violazione della sicurezza o trova un carattere null. Questo è un problema con le funzioni della stringa C originale, se non si specifica una lunghezza da qualche parte in cui si verificano problemi.
contattare il

4
strtok richiede un puntatore a un array di caratteri con terminazione null non const, che non è una creatura comune trovare nel codice c ++ ... qual è il tuo modo preferito per convertirlo in uno std :: string?
fuzzy

105

Un altro modo rapido è usare getline. Qualcosa di simile a:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

Se lo desideri, puoi creare un split()metodo semplice per restituire a vector<string>, il che è davvero utile.


2
Ho avuto problemi nell'usare questa tecnica con caratteri 0x0A nella stringa che ha reso prematuramente chiuso il ciclo while. Altrimenti, è una bella soluzione semplice e veloce.
Ryan H.

4
Questo va bene, ma devi solo tenere presente che facendo questo il delimitatore predefinito '\ n' non viene considerato. Questo esempio funzionerà, ma se stai usando qualcosa del tipo: while (getline (inFile, word, '')) dove inFile è un oggetto ifstream contenente più righe otterrai risultati divertenti ..
hackrock,

è troppo male getline restituisce il flusso anziché la stringa, rendendolo inutilizzabile negli elenchi di inizializzazione senza memoria temporanea
fuzzyTew

1
Freddo! Nessuna spinta e C ++ 11, buona soluzione a quei progetti legacy là fuori!
Deqing,

1
Questa è la risposta, il nome della funzione è solo un po 'imbarazzante.
Nils,

82

È possibile utilizzare stream, iteratori e algoritmo di copia per farlo in modo abbastanza diretto.

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()
{
  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);
}

17
Trovo quelli std :: irritanti da leggere .. perché non usare "usando"?
user35978,

80
@Vadi: perché la modifica del post di qualcun altro è piuttosto invadente. @pheze: preferisco che stdio sappia da dove viene il mio oggetto, è solo una questione di stile.
Matthieu M.

7
Capisco la tua ragione e penso che in realtà sia una buona scelta se funziona per te, ma da un punto di vista pedagogico sono davvero d'accordo con Pheze. È più facile leggere e comprendere un esempio completamente estraneo come questo con un "utilizzo dello spazio dei nomi std" in alto perché richiede meno sforzi per interpretare le seguenti righe ... specialmente in questo caso perché tutto proviene dalla libreria standard. Puoi renderlo facile da leggere e ovvio da dove provengono gli oggetti con una serie di "using std :: string;" ecc. Soprattutto perché la funzione è così breve.
Cheshirekow,

61
Nonostante i prefissi "std ::" siano irritanti o brutti, è meglio includerli nel codice di esempio in modo che sia completamente chiaro da dove provengano queste funzioni. Se ti danno fastidio, è banale sostituirli con un "utilizzo" dopo aver rubato l'esempio e rivendicarlo come tuo.
dlchambers,

20
Sì! cosa ha detto! la migliore pratica è usare il prefisso std. Qualsiasi base di codice di grandi dimensioni avrà senza dubbio le proprie librerie e spazi dei nomi e l'utilizzo di "using namespace std" ti farà venire il mal di testa quando inizi a causare conflitti nello spazio dei nomi.
Miek,

48

Nessun gente offesa, ma per un semplice problema così, si stanno facendo le cose modo troppo complicato. Ci sono molti motivi per usare Boost . Ma per qualcosa di così semplice, è come colpire una mosca con una slitta da 20 #.

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

Ad esempio (per il caso di Doug),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

E sì, avremmo potuto dividere () restituire un nuovo vettore invece di passarne uno. È banale avvolgere e sovraccaricare. Ma a seconda di cosa sto facendo, trovo spesso meglio riutilizzare oggetti preesistenti anziché crearne sempre di nuovi. (Fintanto che non dimentico di svuotare il vettore in mezzo!)

Riferimento: http://www.cplusplus.com/reference/string/string/ .

(Inizialmente stavo scrivendo una risposta alla domanda di Doug: Modifica ed estrazione di stringhe C ++ basate sui separatori (chiuso) . Ma da quando Martin York ha chiuso quella domanda con un puntatore qui ... Generalizzerò semplicemente il mio codice.)


12
Perché definire una macro che usi solo in un posto. E in che modo il tuo UASSERT è migliore di quello standard. Suddividere il confronto in 3 token come quello non fa altro che richiedere più virgole di quante altrimenti avresti bisogno.
crelbor

1
Forse la macro UASSERT mostra (nel messaggio di errore) la relazione effettiva tra (e valori di) i due valori confrontati? In realtà è una buona idea, IMHO.
GhassanPL,

10
Ugh, perché la std::stringclasse non include una funzione split ()?
Mr. Shickadance,

Penso che l'ultima riga nel ciclo while dovrebbe essere start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());e il ciclo while dovrebbe essere while (start != string::npos). Inoltre, controllo la sottostringa per essere sicuro che non sia vuota prima di inserirla nel vettore.
John K,

@JohnK Se l'input ha due delimitatori consecutivi, allora chiaramente la stringa tra loro è vuota e deve essere inserita nel vettore. Se i valori vuoti non sono accettabili per uno scopo particolare, questa è un'altra cosa, ma IMHO tali vincoli dovrebbero essere applicati al di fuori di questo tipo di funzioni molto generiche.
Lauri Nurmi,

46

Una soluzione che utilizza regex_token_iterators:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}

5
Questa dovrebbe essere la risposta più votata. Questo è il modo giusto per farlo in C ++> = 11.
Onnipotente il

1
Sono contento di aver fatto scorrere fino in fondo questa risposta (al momento avevo solo 9 voti). Questo è esattamente come dovrebbe apparire un codice C ++ 11 per questa attività!
YePhIcK

Ottima risposta che non si basa su librerie esterne e utilizza librerie già disponibili
Andrew,

1
Ottima risposta, offrendo la massima flessibilità nei delimitatori. Alcune avvertenze: l'uso di \ s + regex evita i token vuoti nel mezzo del testo, ma fornisce un primo token vuoto se il testo inizia con uno spazio bianco. Inoltre, regex sembra lento: sul mio laptop, per 20 MB di testo casuale, ci vogliono 0,6 secondi, rispetto a 0,014 secondi per strtok, strsep o la risposta di Parham usando str.find_first_of, o 0,027 secondi per Perl, o 0,021 secondi per Python . Per un breve testo, la velocità potrebbe non essere un problema.
Mark Gates,

2
Ok forse sembra bello, ma questo è chiaramente un uso eccessivo di espressioni regolari. Ragionevole solo se non ti interessano le prestazioni.
Marek R,

35

Boost ha una forte funzione split: boost :: algoritmo :: split .

Programma di esempio:

#include <vector>
#include <boost/algorithm/string.hpp>

int main() {
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;
}

Produzione:

"a"
"b"
" c "
""
"e"
"f"
""

26

So che hai chiesto una soluzione C ++, ma potresti considerarlo utile:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

Il vantaggio rispetto a Boost in questo esempio è che è un mapping diretto uno a uno al codice del tuo post.

Vedi di più nella documentazione di Qt


22

Ecco una classe di tokenizer di esempio che potrebbe fare quello che vuoi

//Header file
class Tokenizer 
{
    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
};

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) {}

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) {}

bool Tokenizer::NextToken() 
{
    return NextToken(m_delimiters);
}

bool Tokenizer::NextToken(const std::string& delimiters) 
{
    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    {
        m_offset = m_string.length();
        return false;
    }

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    {
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    }

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;
}

Esempio:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}

19

Questa è una semplice soluzione solo STL (~ 5 righe!) Che usa std::finde std::find_first_not_ofgestisce le ripetizioni del delimitatore (come spazi o punti per esempio), così come i delimitatori iniziali e finali:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}

Provalo vivo !


3
Questo è buono ma penso che devi usare find_first_of () invece di find () per farlo funzionare correttamente con più delimitatori.

2
@ user755921 più delimitatori vengono ignorati quando si trova la posizione iniziale con find_first_not_of.
Principiante

16

pystring è una piccola libreria che implementa un sacco di funzioni di stringa di Python, incluso il metodo split:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");

3
Caspita, hai risposto alla mia domanda immediata e a molte domande future. Ho capito che c ++ è potente. Ma quando la divisione di una stringa si traduce in codice sorgente come le risposte sopra, è chiaramente scoraggiante. Mi piacerebbe conoscere altre biblioteche come questa che abbassano le comodità di livello superiore.
Ross,

caspita, hai appena fatto la mia giornata !! non sapeva di pystring. questo mi farà risparmiare un sacco di tempo!
scadono il

11

Ho pubblicato questa risposta per una domanda simile.
Non reinventare la ruota. Ho usato un certo numero di librerie e la più veloce e flessibile che abbia mai incontrato è: C ++ String Toolkit Library .

Ecco un esempio di come usarlo che ho pubblicato altrove sullo stackoverflow.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
       std::string s("Somewhere down the road");
       std::vector<std::string> result;
       if( strtk::parse( s, whitespace, result ) )
       {
           for(size_t i = 0; i < result.size(); ++i )
            std::cout << result[i] << std::endl;
       }
    }

    {  // parsing a string into a vector of floats with other separators
       // besides spaces

       std::string s("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       {
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       }
    }

    {  // parsing a string into specific variables

       std::string s("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       {
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       }
    }

    return 0;
}

8

Guarda questo esempio. Potrebbe aiutarti ..

#include <iostream>
#include <sstream>

using namespace std;

int main ()
{
    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) {
        is >> tmps;
        cout << tmps << "\n";
    }
    return 0;
}

1
Vorrei farewhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix il

6

MFC / ATL ha un tokenizer molto bello. Da MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
};

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third

1
Questa funzione Tokenize () salterà i token vuoti, ad esempio, se nella stringa principale è presente la sottostringa "%%", non viene restituito alcun token vuoto. È saltato.
Sheen

4

Se vuoi usare C, puoi usare la funzione strtok . Dovresti prestare attenzione ai problemi multi-threading quando lo usi.


3
Nota che strtok modifica la stringa che stai controllando, quindi non puoi usarla su stringhe const char * senza fare una copia.
Graeme Perrow,

9
Il problema del multithreading è che strtok usa una variabile globale per tenere traccia di dove si trova, quindi se hai due thread che usano ciascuno strtok, otterrai un comportamento indefinito.
JohnMcG,

@JohnMcG O semplicemente usare ciò strtok_sche è fondamentalmente strtokcon passaggio esplicito dello stato.
Matthias,

4

Per cose semplici uso solo quanto segue:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)
{
    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    {
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        {
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        }

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    }

    if (prev_pos < i_source.length())
    {
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    }

    return number_of_tokens;
}

Disclaimer codardo: scrivo software di elaborazione dati in tempo reale in cui i dati arrivano attraverso file binari, socket o alcune chiamate API (schede I / O, fotocamera). Non utilizzo mai questa funzione per qualcosa di più complicato o critico in termini di tempo rispetto alla lettura di file di configurazione esterni all'avvio.


4

Puoi semplicemente usare una libreria di espressioni regolari e risolverla usando espressioni regolari.

Usa expression (\ w +) e la variabile in \ 1 (o $ 1 a seconda dell'implementazione della libreria di espressioni regolari).


+1 per suggerire regex, se non hai bisogno della velocità di curvatura è la soluzione più flessibile, non ancora supportata ovunque ma col passare del tempo diventerà meno importante.
odinthenerd

+1 da parte mia, ho appena provato <regex> in c ++ 11. Così semplice ed elegante
StahlRat

4

Molti suggerimenti eccessivamente complicati qui. Prova questa semplice soluzione std :: string:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}

4

Ho pensato che fosse quello a cui l' >>operatore su stream di string era:

string word; sin >> word;

1
Colpa mia per aver dato un cattivo esempio (troppo semplice). Per quanto ne so, funziona solo quando il delimitatore è uno spazio bianco.
Bill the Lizard,

4

La risposta di Adam Pierce fornisce un tokenizer filato a mano che contiene a const char*. È un po 'più problematico avere a che fare con gli iteratori perché l' incremento di un stringiteratore finale non è definito . Detto questo, dato string str{ "The quick brown fox" }che possiamo certamente realizzare questo:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Live Example


Se stai cercando una complessità astratta utilizzando la funzionalità standard, come suggerisce On Freund strtok è un'opzione semplice:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

Se non hai accesso a C ++ 17 dovrai sostituire data(str)come in questo esempio: http://ideone.com/8kAGoa

Sebbene non sia dimostrato nell'esempio, strtoknon è necessario utilizzare lo stesso delimitatore per ciascun token. Insieme a questo vantaggio, ci sono diversi svantaggi:

  1. strtoknon possono essere utilizzati contemporaneamente su più strings: O nullptrdeve essere passato a per continuare a tokenizzare la corrente stringo char*deve essere passato un nuovo tokenize (ci sono comunque alcune implementazioni non standard che supportano questo, come ad esempio:strtok_s )
  2. Per lo stesso motivo strtoknon può essere utilizzato contemporaneamente su più thread (ciò può tuttavia essere definito dall'implementazione, ad esempio: l'implementazione di Visual Studio è thread-safe )
  3. La chiamata strtokmodifica la stringfunzione su cui sta operando, quindi non può essere utilizzata su stringhe const strings, const char*s o letterali, per tokenizzare una di queste strtoko per operare su un stringcontenuto che deve essere preservato, strdovrebbe essere copiato, quindi la copia potrebbe essere operato

ci fornisce split_viewtokenize stringhe, in modo non distruttivo: https://topanswers.xyz/cplusplus?q=749#a874


I metodi precedenti non possono generare un token vectorsul posto, il che significa che senza inizializzarli in una funzione di supporto che non possono essere inizializzati const vector<string> tokens. Quella funzionalità e la capacità di accettare qualsiasi delimitatore di spazi bianchi possono essere sfruttate usando un istream_iterator. Ad esempio dato: const string str{ "The quick \tbrown \nfox" }possiamo fare questo:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Live Example

La costruzione richiesta di istringstreamun'opzione per ha un costo molto maggiore rispetto alle precedenti 2 opzioni, tuttavia questo costo è in genere nascosto nelle spese di stringallocazione.


Se nessuna delle opzioni di cui sopra è sufficientemente flessibile per le tue esigenze di tokenizzazione, l'opzione più flessibile sta regex_token_iteratorovviamente utilizzando una flessibilità che comporta costi maggiori, ma di nuovo è probabilmente nascosto nel stringcosto di allocazione. Diciamo ad esempio che vogliamo tokenizzare sulla base di virgole non di escape, anche mangiando spazi bianchi, dato il seguente input: const string str{ "The ,qu\\,ick ,\tbrown, fox" }possiamo fare questo:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Live Example


strtok_sè C11 standard, a proposito. strtok_rè uno standard POSIX2001. Tra entrambi, esiste una versione standard di rientro strtokper la maggior parte delle piattaforme.
Andon M. Coleman,

@ AndonM.Coleman Ma questa è una domanda c ++ e in C ++ #include <cstring>include solo la versione c99 di strtok. Quindi la mia ipotesi è che stai solo fornendo questo commento come materiale di supporto, dimostrando la disponibilità specifica delle strtokestensioni per l'implementazione ?
Jonathan Mee,

1
Solo che non è così non standard come la gente potrebbe altrimenti credere. strtok_sè fornito sia da C11 sia come estensione standalone nel runtime C di Microsoft. C'è un curioso po 'di storia qui in cui le _sfunzioni di Microsoft sono diventate lo standard C.
Andon M. Coleman,

@ AndonM.Coleman Giusto, sono con te. Ovviamente se è nello standard C11 l'interfaccia e l'implementazione hanno dei vincoli che richiedono comportamenti identici indipendentemente dalla piattaforma. Ora l'unico problema è garantire che la funzione C11 sia disponibile per noi su più piattaforme. Speriamo che lo standard C11 sia qualcosa che C ++ 17 o C ++ 20 scelgono per il ritiro.
Jonathan Mee,

3

So che questa domanda ha già una risposta, ma voglio contribuire. Forse la mia soluzione è un po 'semplice ma questo è quello che mi è venuto in mente:

vector<string> get_words(string const& text, string const& separator)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(separator);

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + separator.length());
        second_pos = tmp.find(separator);
    }

    result.push_back(tmp);

    return result;
}

Si prega di commentare se c'è un approccio migliore a qualcosa nel mio codice o se qualcosa non va.

AGGIORNAMENTO: aggiunto separatore generico


Hai usato la tua soluzione dalla folla :) Posso modificare il codice per aggiungere un separatore?
Zac,

1
@Zac sono contento che ti sia piaciuto e spesso puoi modificarlo ... basta aggiungere una sezione di aggiornamento in grassetto alla mia risposta ...
NutCracker

2

Ecco un approccio che ti consente di controllare se i token vuoti sono inclusi (come strsep) o esclusi (come strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}

2

Mi sembra strano che con tutti noi secchioni attenti alla velocità qui su SO nessuno abbia presentato una versione che utilizza una tabella di ricerca generata dal tempo di compilazione per il delimitatore (esempio di implementazione più in basso). Utilizzando una tabella di ricerca e gli iteratori dovrebbero battere std :: regex in efficienza, se non hai bisogno di battere regex, basta usarlo, il suo standard a partire da C ++ 11 e super flessibile.

Alcuni hanno già suggerito regex ma per i noob ecco un esempio impacchettato che dovrebbe fare esattamente ciò che l'OP si aspetta:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

Se dobbiamo essere più veloci e accettare il vincolo che tutti i caratteri devono essere 8 bit, possiamo creare una tabella di ricerca al momento della compilazione usando la metaprogrammazione:

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

Con quello sul posto fare una getNextTokenfunzione è facile:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

Usarlo è anche facile:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

Ecco un esempio dal vivo: http://ideone.com/GKtkLQ


1
È possibile tokenizzare con un delimitatore di stringa?
Galigator,

questa versione è ottimizzata solo per i delimitatori a carattere singolo, l'uso di una tabella di ricerca non è adatto per i delimitatori a più caratteri (stringa), quindi è più difficile battere regex in termini di efficienza.
venerdì

1

puoi approfittare di boost :: make_find_iterator. Qualcosa di simile a questo:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}

1

Ecco il mio Swiss® Army Knife di tokenizer per stringhe per suddividere le stringhe in base agli spazi bianchi, tenendo conto delle stringhe avvolte a virgoletta singola e doppia, nonché di eliminare quei caratteri dai risultati. Ho usato RegexBuddy 4.x per generare la maggior parte dello snippet di codice, ma ho aggiunto una gestione personalizzata per le citazioni di stripping e poche altre cose.

#include <string>
#include <locale>
#include <regex>

std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
    std::vector<std::wstring> tokens;

    std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);

    std::wsregex_iterator next( string_to_tokenize.begin(),
                                string_to_tokenize.end(),
                                re,
                                std::regex_constants::match_not_null );

    std::wsregex_iterator end;
    const wchar_t single_quote = L'\'';
    const wchar_t double_quote = L'\"';
    while ( next != end ) {
        std::wsmatch match = *next;
        const std::wstring token = match.str( 0 );
        next++;

        if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
            tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
        else
            tokens.emplace_back(token);
    }
    return tokens;
}

1
(Giù) i voti possono essere altrettanto costruttivi dei voti positivi, ma non quando non lasci commenti sul perché ...
kayleeFrye_onDeck

1
Ti ho uniformato, ma potrebbe essere perché il codice sembra piuttosto scoraggiante per il programmatore che cerca su "come dividere una stringa" soprattutto senza documentazione
mattshu,

Grazie @mattshu! Sono i segmenti regex che lo rendono scoraggiante o qualcos'altro?
kayleeFrye_onDeck

0

Se è nota la lunghezza massima della stringa di input da tokenizzare, è possibile sfruttarla e implementare una versione molto veloce. Sto abbozzando l'idea di base di seguito, che è stata ispirata da strtok () e dalla struttura dei dati "suffix array", descritta nella seconda edizione di "Programming Perls" di Jon Bentley, capitolo 15. La classe C ++ in questo caso offre solo un po 'di organizzazione e convenienza d'uso. L'implementazione mostrata può essere facilmente estesa per rimuovere i caratteri spazi bianchi iniziali e finali nei token.

Fondamentalmente si possono sostituire i caratteri di separazione con caratteri "\ 0" che terminano la stringa e impostare i puntatori sui token all'interno della stringa modificata. Nel caso estremo in cui la stringa è composta solo da separatori, si ottiene la lunghezza della stringa più 1 risultante token vuoti. È pratico duplicare la stringa da modificare.

File di intestazione:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    {
        return mNumTokens;
    }

    const char *    GetToken( const size_t token_idx ) const
    {
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    }

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    {
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    }
};

File di implementazione:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )
{
    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();
}

TextLineSplitter::~TextLineSplitter()
{
    delete [] mBuff;
    delete [] mTokens;
}


void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )
{
    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    {
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        {
            mTokens[ mNumTokens ] = &mBuff[ idx ];
        } // if

        if( chr == sep_char || chr == '\0' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

Uno scenario di utilizzo sarebbe:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
    printf( "%s\n", spl.GetToken( i ) );
}

produzione:

Item1

Item2
Item3

0

boost::tokenizerè tuo amico, ma considera di rendere il tuo codice portatile con riferimento ai problemi di internazionalizzazione (i18n) utilizzando wstring/ wchar_tanziché i legacy string/ chartipi.

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()
{
  wstring s;
  while (getline(wcin, s)) {
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
      wcout << *beg << L"\t"; // output (or store in vector)
    }
    wcout << L"\n";
  }
  return 0;
}

"legacy" non è assolutamente corretto ed wchar_tè un tipo dipendente dall'implementazione orribile che nessuno dovrebbe usare se non assolutamente necessario.
CoffeeandCode

L'uso di wchar_t in qualche modo non risolve automaticamente i problemi di i18n. Usa le codifiche per risolvere quel problema. Se stai dividendo una stringa per un delimitatore, è implicito che il delimitatore non si scontra con il contenuto codificato di alcun token all'interno della stringa. Potrebbe essere necessario scappare, ecc. Wchar_t non è una soluzione magica a questo.
yonil,

0

Semplice codice C ++ (standard C ++ 98), accetta più delimitatori (specificati in uno std :: string), utilizza solo vettori, stringhe e iteratori.

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.