Come posso scorrere le parole di una stringa?


2987

Sto cercando di scorrere le parole di una stringa.

Si può presumere che la stringa sia composta da parole separate da spazi bianchi.

Nota che non mi interessano le funzioni della stringa C o quel tipo di manipolazione / accesso ai caratteri. Inoltre, si prega di dare la precedenza all'eleganza rispetto all'efficienza nella risposta.

La migliore soluzione che ho in questo momento è:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

C'è un modo più elegante per farlo?


617
Amico ... L'eleganza è solo un modo elegante per dire "efficienza che sembra carina" nel mio libro. Non rifuggire dall'uso delle funzioni C e dei metodi rapidi per realizzare qualsiasi cosa solo perché non è contenuta in un modello;)

14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon,

21
@Eduardo: anche quello è sbagliato ... devi provare iss tra il tentativo di trasmettere un altro valore e l'utilizzo di quel valore, cioèstring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy,

9
Varie opzioni in C ++ per fare ciò per impostazione predefinita: cplusplus.com/faq/sequences/strings/split
hB0

14
C'è di più nell'eleganza oltre alla semplice efficienza. Gli attributi eleganti includono un numero di righe basso e un'alta leggibilità. IMHO Elegance non è un proxy per l'efficienza ma la manutenibilità.
Matt

Risposte:


1369

Per quello che vale, ecco un altro modo per estrarre i token da una stringa di input, basandosi solo sulle funzionalità di libreria standard. È un esempio della potenza e dell'eleganza alla base del design della STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

Invece di copiare i token estratti in un flusso di output, è possibile inserirli in un contenitore, utilizzando lo stesso copyalgoritmo generico .

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... o crea vectordirettamente:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

164
È possibile specificare un delimitatore per questo? Come ad esempio la divisione in virgole?
l3dx,

15
@Jonathan: \ n non è il delimitatore in questo caso, è il deliminer per l'output di cout.
huy,

772
Questa è una soluzione scadente in quanto non richiede alcun altro delimitatore, quindi non scalabile e non mantenibile.
HelloWorld,

37
In realtà, questo può funzionare bene con altri delimitatori (anche se fare alcuni è un po 'brutto). Si crea una sfaccettatura del tipo che classifica i delimitatori desiderati come spazi bianchi, si crea una locale che contiene quella sfaccettatura, quindi si infonde lo stringstream con quella locale prima di estrarre le stringhe.
Jerry Coffin,

53
@Kinderchocolate "Si può presumere che la stringa sia composta da parole separate da spazi bianchi" - Hmm, non sembra una cattiva soluzione al problema della domanda. "non scalabile e non mantenibile" - Ah, bello.
Christian Rau,

2426

Lo uso per dividere la stringa da un delimitatore. Il primo inserisce i risultati in un vettore pre-costruito, il secondo restituisce un nuovo vettore.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Nota che questa soluzione non salta i token vuoti, quindi di seguito troverai 4 elementi, uno dei quali è vuoto:

std::vector<std::string> x = split("one:two::three", ':');

86
Per evitare che salti i token vuoti, empty()verifica:if (!item.empty()) elems.push_back(item)
0x499602D2

11
Che ne dici del delim contiene due caratteri come ->?
Herohuyongtao,

7
@herohuyongtao, questa soluzione funziona solo per i delimitatori a singolo carattere.
Evan Teran,

4
@JeshwanthKumarNK, non è necessario, ma ti consente di fare cose come passare il risultato direttamente a una funzione come questa: f(split(s, d, v))pur avendo comunque il vantaggio di una pre-allocazione vector.
Evan Teran,

8
Avvertenza: split ("one: two :: three", ':') e split ("one: two :: three:", ':') restituiscono lo stesso valore.
Dshin,

834

Una possibile soluzione che utilizza Boost potrebbe essere:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Questo approccio potrebbe essere persino più veloce stringstreamdell'approccio. E poiché si tratta di una funzione modello generica, può essere utilizzata per dividere altri tipi di stringhe (wchar, ecc. O UTF-8) utilizzando tutti i tipi di delimitatori.

Vedere la documentazione per i dettagli.


35
Qui la velocità è irrilevante, poiché entrambi questi casi sono molto più lenti di una funzione simile a uno strtok.
Tom

45
E per coloro che non hanno già il boost ... bcp copia oltre 1.000 file per questo :)
Roman Starkov

12
Avviso, quando viene fornita una stringa vuota (""), questo metodo restituisce un vettore contenente la "" stringa. Quindi aggiungi un "if (! String_to_split.empty ())" prima della divisione.
Offirmo,

29
@Ian Gli sviluppatori integrati non utilizzano tutti boost.
ACK_stoverflow

31
come addendum: uso boost solo quando devo, normalmente preferisco aggiungere alla mia libreria di codice che è autonoma e portatile in modo da poter ottenere un piccolo codice specifico preciso, che raggiunga un determinato obiettivo. In questo modo il codice è non pubblico, performante, banale e portatile. Boost ha il suo posto, ma suggerirei che è un po 'eccessivo per le stringhe token: non avresti la tua intera casa trasportata in una società di ingegneria per ottenere un nuovo chiodo martellato nel muro per appendere un quadro .... potrebbero farlo estremamente bene, ma il prosare è di gran lunga superato dai contro.
GMasucci,

362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

12
È inoltre possibile dividere su altri delimitatori se si utilizza getlinenella whilecondizione, ad esempio per dividere per virgole, utilizzare while(getline(ss, buff, ',')).
Ali,

181

Per coloro con i quali non sta bene sacrificare tutta l'efficienza per la dimensione del codice e vedere "efficiente" come un tipo di eleganza, il seguente dovrebbe colpire un punto debole (e penso che la classe contenitore modello sia un'aggiunta incredibilmente elegante.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Di solito scelgo di usare i std::vector<std::string>tipi come secondo parametro ( ContainerT) ... ma list<>è molto più veloce di vector<>quando non è necessario l'accesso diretto, e puoi persino creare la tua classe di stringhe e usare qualcosa come std::list<subString>dove subStringnon fare copie per una velocità incredibile aumenta.

È più del doppio più veloce del tokenize più veloce in questa pagina e quasi 5 volte più veloce di alcuni altri. Inoltre con i tipi di parametri perfetti è possibile eliminare tutte le copie di stringhe ed elenchi per ulteriori aumenti di velocità.

Inoltre, non restituisce il risultato (estremamente inefficiente), ma passa i token come riferimento, consentendo in tal modo di creare token utilizzando più chiamate se lo desideri.

Infine, consente di specificare se tagliare i token vuoti dai risultati tramite un ultimo parametro facoltativo.

Tutto ciò di cui ha bisogno è std::string... il resto è facoltativo. Non utilizza stream o la libreria boost, ma è abbastanza flessibile da poter accettare alcuni di questi tipi stranieri in modo naturale.


5
Sono un grande fan di questo, ma per g ++ (e probabilmente buona pratica) chiunque lo usi vorrà typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; typedefs e nomi di tipi : Quindi sostituire value_type e size_types di conseguenza.
dal

11
Per quelli di noi per i quali il modello e il primo commento sono completamente estranei, un esempio di utilizzo cmplete con le inclusioni richieste sarebbe adorabile.
Wes Miller,

3
Ahh bene, l'ho capito. Ho inserito le righe C ++ dal commento di aws all'interno del corpo della funzione di tokenize (), quindi ho modificato le righe tokens.push_back () per cambiare il ContainerT :: value_type in appena ValueType e ho cambiato (ContainerT :: value_type :: size_type) in ( SizeType). Risolti i problemi con cui g ++ si lamentava. Richiamalo semplicemente come tokenize (some_string, some_vector);
Wes Miller,

2
Oltre a eseguire alcuni test delle prestazioni sui dati di esempio, in primo luogo li ho ridotti al minor numero possibile di istruzioni e anche al minor numero possibile di copie di memoria abilitate dall'uso di una classe di sottostringa che fa riferimento solo a offset / lunghezze in altre stringhe. (Ho fatto il mio, ma ci sono altre implementazioni). Sfortunatamente non c'è molto altro da fare per migliorare, ma sono stati possibili aumenti incrementali.
Marius,

3
Questo è l'output corretto per quando trimEmpty = true. Tieni presente che "abo"non è un delimitatore in questa risposta, ma l'elenco dei caratteri delimitatore. Sarebbe semplice modificarlo per prendere una singola stringa di caratteri delimitatore (penso che str.find_first_ofdovrebbe cambiare in str.find_first, ma potrei sbagliarmi ... non posso testare)
Marius

158

Ecco un'altra soluzione. È compatto e ragionevolmente efficiente:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Può essere facilmente modellato per gestire separatori di stringhe, stringhe larghe, ecc.

Notare che la divisione ""risulta in una singola stringa vuota e la divisione ","(es. Sep) genera due stringhe vuote.

Può anche essere facilmente espanso per saltare i token vuoti:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Se si desidera dividere una stringa in più delimitatori mentre si saltano i token vuoti, è possibile utilizzare questa versione:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

10
La prima versione è semplice e fa il lavoro alla perfezione. L'unica modifica che vorrei fare sarebbe quella di restituire il risultato direttamente, invece di passarlo come parametro.
Gregreglom,

2
L'output viene passato come parametro per l'efficienza. Se il risultato fosse restituito, richiederebbe una copia del vettore o un'allocazione di heap che dovrebbe essere liberata.
Alec Thomas,

2
Un leggero addendum al mio commento sopra: questa funzione potrebbe restituire il vettore senza penalità se si utilizza la semantica di spostamento C ++ 11.
Alec Thomas,

7
@AlecThomas: anche prima di C ++ 11, la maggior parte dei compilatori non ottimizzerebbe la copia di ritorno tramite NRVO? (+1 comunque; molto succinto)
Marcelo Cantos,

11
Di tutte le risposte questo sembra essere uno dei più accattivanti e flessibili. Insieme alla getline con un delimitatore, sebbene sia una soluzione meno ovvia. Lo standard c ++ 11 non ha nulla per questo? Al giorno d'oggi c ++ 11 supporta le schede perforate?
Spacen Jasset,

123

Questo è il mio modo preferito di scorrere una stringa. Puoi fare quello che vuoi per parola.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

È possibile dichiarare wordcome char?
abatishchev,

Mi dispiace abatishchev, C ++ non è il mio punto di forza. Ma immagino che non sarebbe difficile aggiungere un ciclo interno per scorrere tutti i caratteri di ogni parola. Ma in questo momento credo che l'attuale ciclo dipenda da spazi per la separazione delle parole. A meno che tu non sappia che c'è un solo carattere tra ogni spazio, nel qual caso puoi semplicemente lanciare una "parola" su un carattere ... mi dispiace non posso essere di maggiore aiuto, ho intenzione di rispolverare il mio C ++
gnomed

11
se dichiari una parola come carattere, itererà su ogni carattere non bianco. È abbastanza semplice provare:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner il

79

Questo è simile alla domanda Stack Overflow Come posso tokenizzare una stringa in C ++? .

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

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

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

Questo materializza una copia di tutti i token o mantiene solo la posizione iniziale e finale del token corrente?
einpoklum,

66

Mi piace quanto segue perché mette i risultati in un vettore, supporta una stringa come delim e dà il controllo sul mantenimento di valori vuoti. Ma non sembra buono allora.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Ovviamente, Boost ha un split()funzionamento che funziona parzialmente in questo modo. E, se per "spazio bianco", intendi davvero qualsiasi tipo di spazio bianco, usando la divisione di Boost con le is_any_of()grandi opere.


Finalmente una soluzione che gestisce correttamente i token vuoti su entrambi i lati della stringa
fmuecke,

53

STL non dispone già di tale metodo.

Tuttavia, puoi usare la strtok()funzione di C usando il std::string::c_str()membro oppure puoi scrivere la tua. Ecco un esempio di codice che ho trovato dopo una rapida ricerca su Google ( "Split stringa STL" ):

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.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Preso da: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Se hai domande sull'esempio di codice, lascia un commento e ti spiegherò.

E solo perché non implementa un typedefiteratore chiamato o un sovraccarico l' <<operatore non significa che è un codice errato. Uso le funzioni C abbastanza frequentemente. Ad esempio, printfed scanfentrambi sono più veloci distd::cin e std::cout(significativamente), ilfopen sintassi è molto più amichevole per i tipi binari e tende anche a produrre EXE più piccoli.

Non essere venduto con questo affare "Elegance over performance" .


Sono a conoscenza delle funzioni della stringa C e anche dei problemi di prestazione (entrambi i quali ho notato nella mia domanda). Tuttavia, per questa domanda specifica, sto cercando un'elegante soluzione C ++.
Ashwin Nanjappa,

11
@Nelson LaQuet: Fammi indovinare: perché strtok non è rientrato?
paercebal,

40
@Nelson non passa mai string.c_str () a strtok! strtok elimina la stringa di input (inserisce i caratteri '\ 0' per sostituire ogni delimitatore) e c_str () restituisce una stringa non modificabile.
Evan Teran,

3
@Nelson: quell'array deve essere della dimensione str.size () + 1 nel tuo ultimo commento. Ma concordo con la tua tesi secondo cui è sciocco evitare funzioni C per ragioni "estetiche".
j_random_hacker,

2
@paulm: No, la lentezza dei flussi C ++ è causata dalle sfaccettature. Sono ancora più lenti delle funzioni di stdio.h anche quando la sincronizzazione è disabilitata (e sui flussi di stringhe, che non possono essere sincronizzati).
Ben Voigt,

42

Ecco una funzione suddivisa che:

  • è generico
  • usa il C ++ standard (nessun boost)
  • accetta più delimitatori
  • ignora i token vuoti (può essere facilmente modificato)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Esempio di utilizzo:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

Hai dimenticato di aggiungere alla lista di utilizzo: "estremamente inefficiente"
Xander Tulip

1
@XanderTulip, puoi essere più costruttivo e spiegare come o perché?
Marco M.

3
@XanderTulip: suppongo che ti riferisci ad esso restituendo il vettore in base al valore. L'ottimizzazione del valore di ritorno (RVO, google it) dovrebbe occuparsene. Anche in C ++ 11 è possibile tornare con lo spostamento di riferimento.
Joseph Garvin,

3
Questo può effettivamente essere ulteriormente ottimizzato: invece di .push_back (str.substr (...)) si può usare .emplace_back (str, start, pos - start). In questo modo l'oggetto stringa è costruito nel contenitore e quindi evitiamo un'operazione di spostamento + altri shenanigans eseguiti dalla funzione .substr.
Mihai Bişog,

@zoopp si. Buona idea. VS10 non aveva supporto emplace_back quando ho scritto questo. Aggiornerò la mia risposta. Grazie
Marco M.,

36

Ho una soluzione a 2 righe per questo problema:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Quindi, invece di stampare, puoi inserirlo in un vettore.


35

Ancora un altro modo flessibile e veloce

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Per usarlo con un vettore di stringhe (Modifica: poiché qualcuno ha sottolineato di non ereditare le classi STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

Questo è tutto! E questo è solo un modo per usare il tokenizer, come come contare solo le parole:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Limitato dalla fantasia;)



32

Ecco una soluzione semplice che utilizza solo la libreria regex standard

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

L'argomento regex consente di verificare più argomenti (spazi, virgole, ecc.)

Di solito controllo solo per dividere spazi e virgole, quindi ho anche questa funzione predefinita:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

I "[\\s,]+"controlli per spazi ( \\s) e virgole (, ).

Nota, se vuoi dividere wstringinvece di string,

  • cambia tutto std::regexinstd::wregex
  • cambia tutto sregex_token_iteratorinwsregex_token_iterator

Nota, potresti anche prendere l'argomento stringa come riferimento, a seconda del tuo compilatore.


Questa sarebbe stata la mia risposta preferita, ma std :: regex è rotto in GCC 4.8. Dissero di averlo implementato correttamente in GCC 4.9. Ti sto ancora dando il mio +1
mchiasson il

1
Questo è il mio preferito con piccole modifiche: il vettore è tornato come riferimento come hai detto, e anche gli argomenti "str" ​​e "regex" sono passati da riferimenti. grazie.
QuantumKarl,

1
Le stringhe grezze sono piuttosto utili quando si tratta di schemi regex. In questo modo, non devi usare le sequenze di escape ... Puoi semplicemente usare R"([\s,]+)".
Sam,

26

Usare std::stringstreamcome hai funziona perfettamente e fai esattamente quello che volevi. Se stai solo cercando un modo diverso di fare le cose, puoi usare std::find()/ std::find_first_of()estd::string::substr() .

Ecco un esempio:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

Funziona solo con i delimitatori a carattere singolo. Una semplice modifica consente di lavorare con multicharacter:prev_pos = pos += delimiter.length();
David Doria,

25

Se ti piace usare boost, ma vuoi usare un'intera stringa come delimitatore (anziché singoli caratteri come nella maggior parte delle soluzioni precedentemente proposte), puoi usare il boost_split_iterator.

Codice di esempio incluso modello conveniente:

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

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

20

Ecco una soluzione regex che utilizza solo la libreria regex standard. (Sono un po 'arrugginito, quindi potrebbero esserci alcuni errori di sintassi, ma questa è almeno l'idea generale)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

Risposte simili con un approccio regex forse migliore: qui e qui .
nobar,

20

C'è una funzione chiamata strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

3
strtokproviene dalla libreria standard C, non C ++. Non è sicuro da usare nei programmi multithread. Modifica la stringa di input.
Kevin Panko,

13
Poiché memorizza il puntatore char dalla prima chiamata in una variabile statica, in modo che nelle successive chiamate quando viene passato NULL, ricorda quale puntatore deve essere utilizzato. Se un secondo thread chiama strtokquando un altro thread è ancora in elaborazione, questo puntatore char verrà sovrascritto ed entrambi i thread avranno risultati errati. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko

1
come accennato prima, strtok non è sicuro e anche in C si consiglia l'uso di
strtok_r

4
strtok_r può essere utilizzato se ci si trova in una sezione di codice a cui è possibile accedere. questa è l' unica soluzione di tutto quanto sopra che non è il "rumore di linea", ed è una testimonianza di ciò che, esattamente, è sbagliato nel c ++
Erik Aronesty,

Aggiornato in modo che non ci possano essere obiezioni sulla base della sicurezza del thread da winks C ++.
Erik Aronesty,

17

Lo stringstream può essere utile se devi analizzare la stringa con simboli non spaziali:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

14

Finora ho usato quello in Boost , ma avevo bisogno di qualcosa che non dipende da questo, quindi sono arrivato a questo:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Un buon punto è che in separatorste puoi passare più di un personaggio.


13

Ho creato il mio usando strtok e ho usato boost per dividere una stringa. Il metodo migliore che ho trovato è la libreria C ++ String Toolkit . È incredibilmente flessibile e veloce.

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

Il toolkit ha molta più flessibilità di questo semplice esempio, ma la sua utilità nell'analisi di una stringa in elementi utili è incredibile.


13

Corto ed elegante

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

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

può usare qualsiasi stringa come delimitatore, può anche essere usata con dati binari (std :: string supporta dati binari, inclusi i valori null)

utilizzando:

auto a = split("this!!is!!!example!string", "!!");

produzione:

this
is
!example!string

1
Mi piace questa soluzione perché consente al separatore di essere una stringa e non un carattere, tuttavia sta modificando al posto la stringa, quindi sta forzando la creazione di una copia della stringa originale.
Alessandro Teruzzi,

11

L'ho fatto perché avevo bisogno di un modo semplice per dividere stringhe e stringhe basate su c ... Speriamo che anche qualcun altro possa trovarlo utile. Inoltre non si basa sui token e puoi usare i campi come delimitatori, che è un'altra chiave di cui avevo bisogno.

Sono sicuro che ci sono miglioramenti che possono essere apportati per migliorare ulteriormente la sua eleganza e, per favore, farlo con ogni mezzo

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Esempi:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Uscita:

Questo
è
un
esempio di
cstring

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Per mantenere voci vuote (per impostazione predefinita saranno esclusi i vuoti):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

L'obiettivo era renderlo simile al metodo Split () di C # in cui dividere una stringa è facile come:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Spero che qualcun altro possa trovarlo utile quanto me.


10

Che dire di questo:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

Questa è la risposta migliore qui, se vuoi dividere solo su un singolo carattere delimitatore. La domanda originale voleva dividere su spazi bianchi, vale a dire qualsiasi combinazione di uno o più spazi o schede consecutivi. Hai in realtà risposto stackoverflow.com/questions/53849
Oktalist

10

Questa risposta prende la stringa e la inserisce in un vettore di stringhe. Utilizza la libreria boost.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

9

Ecco un altro modo di farlo ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

9

Mi piace usare i metodi boost / regex per questo compito poiché forniscono la massima flessibilità per specificare i criteri di divisione.

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

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

9

Di recente ho dovuto dividere una parola incapsulata in un cammello in parole chiave. Non ci sono delimitatori, ma solo caratteri superiori.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Ad esempio, questo divide "AQueryTrades" in "A", "Query" e "Trades". La funzione funziona con stringhe strette e larghe. Poiché rispetta le impostazioni internazionali correnti, suddivide "RaumfahrtÜberwachungsVerordnung" in "Raumfahrt", "Überwachungs" e "Verordnung".

La nota std::upperdeve essere realmente passata come argomento del modello di funzione. Quindi il più generalizzato da questa funzione può dividere a delimitatori come ",", ";"o " "anche.


2
Ci sono stati 2 giri. Bello. Sembra come se il mio inglese fosse un po '"tedesco". Tuttavia, il revisionista non ha corretto due bug minori forse perché erano comunque ovvi: std::isupperpoteva essere passato come argomento, no std::upper. In secondo luogo mettere un typenameprima del String::const_iterator.
Andreas Spindler,

9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}

9

Utilizzando std::string_viewe la range-v3libreria di Eric Niebler :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Utilizzando un forloop di intervallo anziché l' ranges::for_eachalgoritmo:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

Sì, la gamma per la base sembra migliore - sono d'accordo
Porsche9II,
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.