Analizzare (dividere) una stringa in C ++ usando il delimitatore di stringa (standard C ++)


363

Sto analizzando una stringa in C ++ usando quanto segue:

using namespace std;

string parsed,input="text to be parsed";
stringstream input_stringstream(input);

if (getline(input_stringstream,parsed,' '))
{
     // do some processing.
}

L'analisi con un singolo delimitatore di caratteri va bene. E se volessi usare una stringa come delimitatore.

Esempio: voglio dividere:

scott>=tiger

con >=come delimitatore in modo che io possa ottenere Scott e Tiger.

Risposte:


576

È possibile utilizzare la std::string::find()funzione per trovare la posizione del delimitatore di stringa, quindi utilizzarestd::string::substr() per ottenere un token.

Esempio:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • La find(const string& str, size_t pos = 0)funzione restituisce la posizione della prima occorrenza di strnella stringa onpos se la stringa non viene trovata.

  • La substr(size_t pos = 0, size_t n = npos)funzione restituisce una sottostringa dell'oggetto, a partire dalla posizione pose dalla lunghezza npos.


Se si dispone di più delimitatori, dopo aver estratto un token, è possibile rimuoverlo (delimitatore incluso) per procedere con le estrazioni successive (se si desidera conservare la stringa originale, basta usare s = s.substr(pos + delimiter.length());):

s.erase(0, s.find(delimiter) + delimiter.length());

In questo modo puoi facilmente eseguire il loop per ottenere ogni token.

Esempio completo

std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";

size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
    token = s.substr(0, pos);
    std::cout << token << std::endl;
    s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;

Produzione:

scott
tiger
mushroom

66
Per coloro che non vogliono modificare la stringa di input, faresize_t last = 0; size_t next = 0; while ((next = s.find(delimiter, last)) != string::npos) { cout << s.substr(last, next-last) << endl; last = next + 1; } cout << s.substr(last) << endl;
hayk.mart

30
NOTA: mushroomuscite al di fuori del loop, ovveros = mushroom
Don Larynx

1
Tali esempi non estraggono l'ultimo token dalla stringa. Un mio esempio che estrae un IpV4 da una stringa: <code> size_t last = 0; size_t next = 0; indice int = 0; while (indice <4) {next = str.find (delimitatore, last); auto number = str.substr (last, next - last); IPv4 [index ++] = atoi (number.c_str ()); last = next + 1; } </code>
nebbia

2
@ hayk.mart Solo una nota, che sarebbe la seguente, è necessario aggiungere 2 non 1 a causa della dimensione del delimitatore che è di 2 caratteri :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; size_t last = 0; size_t next = 0; while ((next = s.find (delimitatore, last))! = std :: string :: npos) {std :: cout << s.substr (last, next-last) << std :: endl; last = next + 2; } std :: cout << s.substr (last) << std :: endl;
ervinbosenbacher,

Per ottenere "tigre", usa std::string token = s.substr(s.find(delimiter) + 1);, se sei sicuro che esista (io uso +1 in lunghezza) ...
gsamaras

64

Questo metodo utilizza std::string::findsenza mutare la stringa originale ricordando l'inizio e la fine del token di sottostringa precedente.

#include <iostream>
#include <string>

int main()
{
    std::string s = "scott>=tiger";
    std::string delim = ">=";

    auto start = 0U;
    auto end = s.find(delim);
    while (end != std::string::npos)
    {
        std::cout << s.substr(start, end - start) << std::endl;
        start = end + delim.length();
        end = s.find(delim, start);
    }

    std::cout << s.substr(start, end);
}

34

È possibile utilizzare la funzione successiva per dividere la stringa:

vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        if (!token.empty()) tokens.push_back(token);
        prev = pos + delim.length();
    }
    while (pos < str.length() && prev < str.length());
    return tokens;
}

5
IMO non funziona come previsto: split("abc","a")restituirà un vettore o una singola stringa "bc", dove penso che avrebbe più senso se avesse restituito un vettore di elementi ["", "bc"]. Usando str.split()in Python, era intuitivo per me che dovesse restituire una stringa vuota nel caso in cui delimfosse stata trovata all'inizio o alla fine, ma questa è solo la mia opinione. Ad ogni modo, penso solo che dovrebbe essere menzionato
kyriakosSt

1
Consiglio vivamente di rimuovere la if (!token.empty()) prevenzione del problema menzionato da @kyriakosSt e di altri problemi relativi ai delimitatori consecutivi.
Steve,

1
Rimuoverei il mio voto se potessi, ma SO non me lo permette. Il problema sollevato da @kyriakosSt è un problema e la rimozione if (!token.empty())non sembra sufficiente per risolverlo.
Bhaller,

1
@bhaller questo sniplet è stato progettato esattamente per saltare frammenti vuoti. Se hai bisogno di mantenere quelli vuoti, temo che tu debba scrivere un'altra implementazione suddivisa. Ti consigliamo di pubblicarlo qui per il bene della comunità.
Sviatoslav,

32

Per delimitatore di stringhe

Dividi la stringa in base a un delimitatore di stringa . Come la divisione della stringa in "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"base al delimitatore di stringa "-+", l'output sarà{"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

// for string delimiter
vector<string> split (string s, string delimiter) {
    size_t pos_start = 0, pos_end, delim_len = delimiter.length();
    string token;
    vector<string> res;

    while ((pos_end = s.find (delimiter, pos_start)) != string::npos) {
        token = s.substr (pos_start, pos_end - pos_start);
        pos_start = pos_end + delim_len;
        res.push_back (token);
    }

    res.push_back (s.substr (pos_start));
    return res;
}

int main() {
    string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
    string delimiter = "-+";
    vector<string> v = split (str, delimiter);

    for (auto i : v) cout << i << endl;

    return 0;
}


Produzione

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Per delimitatore a carattere singolo

Dividi la stringa in base a un delimitatore di caratteri. Come la divisione della stringa "adsf+qwer+poui+fdgh"con il delimitatore "+"verrà generato{"adsf", "qwer", "poui", "fdg"h}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

vector<string> split (const string &s, char delim) {
    vector<string> result;
    stringstream ss (s);
    string item;

    while (getline (ss, item, delim)) {
        result.push_back (item);
    }

    return result;
}

int main() {
    string str = "adsf+qwer+poui+fdgh";
    vector<string> v = split (str, '+');

    for (auto i : v) cout << i << endl;

    return 0;
}


Produzione

adsf
qwer
Poui
fdgh

Stai tornando, vector<string>penso che chiamerà il costruttore di copie.
Mayur,

2
Ogni riferimento che ho visto mostra che la chiamata al costruttore della copia viene eliminata in quel contesto.
David, dato il

Con i compilatori "moderni" (C ++ 03?) Credo che sia corretto, RVO e / o spostare la semantica elimineranno il costruttore di copie.
Kevin,

Ho provato quello per il delimitatore a carattere singolo e se la stringa termina in un delimitatore (ovvero una colonna CSV vuota alla fine della riga), non restituisce la stringa vuota. Restituisce semplicemente una stringa in meno. Ad esempio: 1,2,3,4 \ nA, B, C,
kounoupis il

Ho anche provato quello per il delimitatore di stringa e se la stringa termina in un delimitatore, l'ultimo delimitatore diventa parte dell'ultima stringa estratta.
kounoupis,

20

Questo codice divide le linee dal testo e aggiunge tutti in un vettore.

vector<string> split(char *phrase, string delimiter){
    vector<string> list;
    string s = string(phrase);
    size_t pos = 0;
    string token;
    while ((pos = s.find(delimiter)) != string::npos) {
        token = s.substr(0, pos);
        list.push_back(token);
        s.erase(0, pos + delimiter.length());
    }
    list.push_back(s);
    return list;
}

Chiamato da:

vector<string> listFilesMax = split(buffer, "\n");

funziona alla grande! Ho aggiunto list.push_back (s); perché mancava.
Stoica Mircea,

1
manca l'ultima parte della stringa. Al termine del ciclo while, è necessario aggiungere il resto di s come nuovo token.
Whihathac,

Ho apportato una modifica all'esempio di codice per correggere il push_back mancante.
tasto

1
Sarà più bellovector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok ti permette di passare più caratteri come delimitatori. Scommetto che se avessi passato "> =" la tua stringa di esempio sarebbe stata divisa correttamente (anche se> e = sono contati come delimitatori individuali).

MODIFICA se non si desidera utilizzare c_str()per convertire da stringa a carattere *, è possibile utilizzare substr e find_first_of per tokenize.

string token, mystring("scott>=tiger");
while(token != mystring){
  token = mystring.substr(0,mystring.find_first_of(">="));
  mystring = mystring.substr(mystring.find_first_of(">=") + 1);
  printf("%s ",token.c_str());
}

3
Grazie. Ma voglio usare solo C ++ e non tutte le funzioni C come strtok()mi richiederebbe l'utilizzo di array di caratteri anziché stringa.
TheCrazyProgrammer

2
@TheCrazyProgrammer Quindi? Se una funzione C fa quello che ti serve, usala. Questo non è un mondo in cui le funzioni C non sono disponibili in C ++ (in effetti, devono essere). .c_str()è anche economico e facile.
Qix - MONICA È STATA MISTREATA il

1
Il controllo per if (token! = Mystring) fornisce risultati errati se si hanno elementi ripetitivi nella stringa. Ho usato il tuo codice per creare una versione che non ha questo. Ha molte modifiche che cambiano radicalmente la risposta, quindi ho scritto la mia risposta invece di modificarla. Controllalo sotto.
Amber Elferink,

5

Ecco la mia opinione su questo. Gestisce i casi limite e accetta un parametro opzionale per rimuovere le voci vuote dai risultati.

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.size() >= suffix.size() &&
           s.substr(s.size() - suffix.size()) == suffix;
}

std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool& removeEmptyEntries = false)
{
    std::vector<std::string> tokens;

    for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
    {
         size_t position = s.find(delimiter, start);
         end = position != string::npos ? position : s.length();

         std::string token = s.substr(start, end - start);
         if (!removeEmptyEntries || !token.empty())
         {
             tokens.push_back(token);
         }
    }

    if (!removeEmptyEntries &&
        (s.empty() || endsWith(s, delimiter)))
    {
        tokens.push_back("");
    }

    return tokens;
}

Esempi

split("a-b-c", "-"); // [3]("a","b","c")

split("a--c", "-"); // [3]("a","","c")

split("-b-", "-"); // [3]("","b","")

split("--c--", "-"); // [5]("","","c","","")

split("--c--", "-", true); // [1]("c")

split("a", "-"); // [1]("a")

split("", "-"); // [1]("")

split("", "-", true); // [0]()

4

Questo dovrebbe funzionare perfettamente per i delimitatori di stringa (o singolo carattere). Non dimenticare di includere #include <sstream>.

std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+"; 
std::istringstream ss(input);
std::string token;
std::string::iterator it;

while(std::getline(ss, token, *(it = delimiter.begin()))) {
    while(*(++it)) ss.get();
    std::cout << token << " " << '\n';
}

Il primo ciclo while estrae un token usando il primo carattere del delimitatore di stringa. Il secondo ciclo while salta il resto del delimitatore e si ferma all'inizio del token successivo.


3

Vorrei usare boost::tokenizer. Ecco la documentazione che spiega come eseguire una funzione tokenizer appropriata: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Eccone uno che funziona per il tuo caso.

struct my_tokenizer_func
{
    template<typename It>
    bool operator()(It& next, It end, std::string & tok)
    {
        if (next == end)
            return false;
        char const * del = ">=";
        auto pos = std::search(next, end, del, del + 2);
        tok.assign(next, pos);
        next = pos;
        if (next != end)
            std::advance(next, 2);
        return true;
    }

    void reset() {}
};

int main()
{
    std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
    for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
        std::cout << i << '\n';
}

3
Grazie. Ma desidero solo C ++ standard e non una libreria di terze parti.
TheCrazyProgrammer

@TheCrazyProgrammer: Ok, quando ho letto "Standard C ++", ho pensato che non significava estensioni non standard, non che non potevi usare librerie di terze parti conformi agli standard.
Benjamin Lindley,

3

La risposta è già lì, ma la risposta selezionata utilizza la funzione di cancellazione che è molto costosa, pensa ad una stringa molto grande (in MB). Pertanto utilizzo la funzione di seguito.

vector<string> split(const string& i_str, const string& i_delim)
{
    vector<string> result;

    size_t found = i_str.find(i_delim);
    size_t startIndex = 0;

    while(found != string::npos)
    {
        string temp(i_str.begin()+startIndex, i_str.begin()+found);
        result.push_back(temp);
        startIndex = found + i_delim.size();
        found = i_str.find(i_delim, startIndex);
    }
    if(startIndex != i_str.size())
        result.push_back(string(i_str.begin()+startIndex, i_str.end()));
    return result;      
}

Ho provato questo e funziona. Grazie! A mio avviso, questa è la risposta migliore perché, come afferma il risponditore originale, questa soluzione riduce il sovraccarico di memoria e il risultato è convenientemente memorizzato in un vettore. (replica il string.split()metodo Python .)
Robbie Capps,

2

Questo è un metodo completo che divide la stringa su qualsiasi delimitatore e restituisce un vettore delle stringhe tritate.

È un adattamento dalla risposta di ryanbwork. Tuttavia, il suo controllo per: if(token != mystring)dà risultati errati se si hanno elementi ripetitivi nella stringa. Questa è la mia soluzione a questo problema.

vector<string> Split(string mystring, string delimiter)
{
    vector<string> subStringList;
    string token;
    while (true)
    {
        size_t findfirst = mystring.find_first_of(delimiter);
        if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
        {
            subStringList.push_back(mystring); //push back the final piece of mystring
            return subStringList;
        }
        token = mystring.substr(0, mystring.find_first_of(delimiter));
        mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
        subStringList.push_back(token);
    }
    return subStringList;
}

1
Qualcosa del genere while (true)è di solito spaventoso da vedere in un pezzo di codice come questo. Personalmente consiglierei di riscriverlo in modo tale che il confronto std::string::npos(o rispettivamente un controllo con mystring.size()) renda while (true)obsoleto.
Joel Bodenmann il

1

Se non si desidera modificare la stringa (come nella risposta di Vincenzo Pii) e si desidera generare anche l'ultimo token, è possibile utilizzare questo approccio:

inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
    std::vector<std::string> ret;
    size_t start = 0;
    size_t end = 0;
    size_t len = 0;
    std::string token;
    do{ end = s.find(delimiter,start); 
        len = end - start;
        token = s.substr(start, len);
        ret.emplace_back( token );
        start += len + delimiter.length();
        std::cout << token << std::endl;
    }while ( end != std::string::npos );
    return ret;
}

0
#include<iostream>
#include<algorithm>
using namespace std;

int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}

void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}

int main(){

string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);

for(int i=0;i<x;i++)
cout<<res[i]<<endl;
  return 0;
}

PS: funziona solo se le lunghezze delle stringhe dopo la divisione sono uguali


Questo usa l'estensione GCC - array di lunghezza variabile.
user202729

0

Funzione:

std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
    std::vector<std::string> vRet;
    int nPos = 0;
    int nLen = sWhat.length();
    int nDelimLen = sDelim.length();
    while (nPos < nLen) {
        std::size_t nFoundPos = sWhat.find(sDelim, nPos);
        if (nFoundPos != std::string::npos) {
            std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
            vRet.push_back(sToken);
            nPos = nFoundPos + nDelimLen;
            if (nFoundPos + nDelimLen == nLen) { // last delimiter
                vRet.push_back("");
            }
        } else {
            std::string sToken = sWhat.substr(nPos, nLen - nPos);
            vRet.push_back(sToken);
            break;
        }
    }
    return vRet;
}

Unità di test:

bool UnitTestSplit::run() {
bool bTestSuccess = true;

    struct LTest {
        LTest(
            const std::string &sStr,
            const std::string &sDelim,
            const std::vector<std::string> &vExpectedVector
        ) {
            this->sStr = sStr;
            this->sDelim = sDelim;
            this->vExpectedVector = vExpectedVector;
        };
        std::string sStr;
        std::string sDelim;
        std::vector<std::string> vExpectedVector;
    };
    std::vector<LTest> tests;
    tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
    tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
    tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));

    for (int i = 0; i < tests.size(); i++) {
        LTest test = tests[i];
        std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
        std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
        compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
        int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
        for (int n = 0; n < nMin; n++) {
            compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
        }
    }

    return bTestSuccess;
}

0
std::vector<std::string> parse(std::string str,std::string delim){
    std::vector<std::string> tokens;
    char *str_c = strdup(str.c_str()); 
    char* token = NULL;

    token = strtok(str_c, delim.c_str()); 
    while (token != NULL) { 
        tokens.push_back(std::string(token));  
        token = strtok(NULL, delim.c_str()); 
    }

    delete[] str_c;

    return tokens;
}

-4
std::vector<std::string> split(const std::string& s, char c) {
  std::vector<std::string> v;
  unsigned int ii = 0;
  unsigned int j = s.find(c);
  while (j < s.length()) {
    v.push_back(s.substr(i, j - i));
    i = ++j;
    j = s.find(c, j);
    if (j >= s.length()) {
      v.push_back(s.substr(i, s,length()));
      break;
    }
  }
  return v;
}

1
Si prega di essere più accurati. Il tuo codice non verrà compilato. Vedi la dichiarazione di "i" e la virgola anziché un punto.
jstuardo,
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.