Analysieren (Teilen) eines Strings in C ++ mit dem String-Trennzeichen (Standard-C ++)


361

Ich analysiere einen String in C ++ wie folgt:

using namespace std;

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

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

Das Parsen mit einem einzelnen Zeichenbegrenzer ist in Ordnung. Aber was ist, wenn ich eine Zeichenfolge als Trennzeichen verwenden möchte?

Beispiel: Ich möchte teilen:

scott>=tiger

mit >=als Trennzeichen, damit ich Scott und Tiger bekommen kann.

Antworten:


575

Mit der std::string::find()Funktion können Sie die Position Ihres Zeichenfolgenbegrenzers ermitteln und anschließend std::string::substr()ein Token abrufen.

Beispiel:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • Die find(const string& str, size_t pos = 0)Funktion gibt die Position des ersten Auftretens strin der Zeichenfolge zurück oder nposwenn die Zeichenfolge nicht gefunden wird.

  • Die substr(size_t pos = 0, size_t n = npos)Funktion gibt einen Teilstring des Objekts zurück, beginnend an Position posund Länge npos.


Wenn Sie mehrere Trennzeichen haben, können Sie dieses nach dem Extrahieren eines Tokens entfernen (einschließlich Trennzeichen), um mit den nachfolgenden Extraktionen fortzufahren (wenn Sie die ursprüngliche Zeichenfolge beibehalten möchten, verwenden Sie einfach s = s.substr(pos + delimiter.length());):

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

Auf diese Weise können Sie leicht eine Schleife erstellen, um jedes Token zu erhalten.

Vollständiges Beispiel

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;

Ausgabe:

scott
tiger
mushroom

66
Für diejenigen, die die Eingabezeichenfolge nicht ändern möchten, tun Siesize_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
HINWEIS: mushroomAusgänge außerhalb der Schleife, dhs = mushroom
Don Larynx

1
Diese Beispiele extrahieren nicht das letzte Token aus der Zeichenfolge. Ein Beispiel von mir, das ein IpV4 aus einer Zeichenfolge extrahiert: <code> size_t last = 0; size_t next = 0; int index = 0; while (Index <4) {next = str.find (Trennzeichen, last); automatische Nummer = strsubstr (letzte, nächste - letzte); IPv4 [index ++] = atoi (number.c_str ()); last = next + 1; } </ code>
rfog

2
@ hayk.mart Nur eine Anmerkung, das wäre die folgende, Sie müssen 2 statt 1 hinzufügen, da das Trennzeichen 2 Zeichen beträgt :): std :: string s = "scott> = tiger> = pilz"; std :: string delimiter = "> ="; size_t last = 0; size_t next = 0; while ((next = s.find (Trennzeichen, last))! = std :: string :: npos) {std :: cout << s.substr (last, next-last) << std :: endl; last = next + 2; } std :: cout << sstr (last) << std :: endl;
Ervinbosenbacher

Um "Tiger" zu bekommen, verwenden Sie std::string token = s.substr(s.find(delimiter) + 1);, wenn Sie sicher sind, dass es existiert (ich benutze +1 in der Länge) ...
gsamaras

64

Diese Methode verwendet, std::string::findohne die ursprüngliche Zeichenfolge zu mutieren, indem der Anfang und das Ende des vorherigen Teilstring-Tokens gespeichert werden.

#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

Mit der nächsten Funktion können Sie die Zeichenfolge teilen:

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 funktioniert es nicht wie erwartet: Gibt split("abc","a")einen Vektor oder eine einzelne Zeichenfolge zurück, "bc"wobei ich denke, dass es sinnvoller wäre, wenn es einen Vektor von Elementen zurückgegeben hätte ["", "bc"]. Bei der Verwendung str.split()in Python war es für mich intuitiv, eine leere Zeichenfolge zurückzugeben, falls delimsie entweder am Anfang oder am Ende gefunden wurde, aber das ist nur meine Meinung. Wie auch immer, ich denke, es sollte erwähnt werden
kyriakosSt

1
if (!token.empty()) Ich würde dringend empfehlen, das von @kyriakosSt erwähnte Problem sowie andere Probleme im Zusammenhang mit aufeinanderfolgenden Trennzeichen zu entfernen .
Steve

1
Ich würde meine Gegenstimme entfernen, wenn ich könnte, aber SO lässt mich nicht. Das von @kyriakosSt angesprochene Problem ist ein Problem, und das Entfernen if (!token.empty())scheint nicht ausreichend zu sein, um es zu beheben.
bhaller

1
@bhaller Dieses Sniplet wurde genau entwickelt, um leere Fragmente zu überspringen. Wenn Sie leere behalten müssen, müssen Sie leider eine weitere Split-Implementierung schreiben. Bitte schlagen Sie vor, es hier zum Wohle der Gemeinschaft zu posten.
Sviatoslav

32

Für Zeichenfolgenbegrenzer

Geteilte Zeichenfolge basierend auf einem Zeichenfolgenbegrenzer . Wenn Sie beispielsweise eine Zeichenfolge "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"basierend auf dem Zeichenfolgenbegrenzer aufteilen "-+", wird die Ausgabe ausgeführt{"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;
}


Ausgabe

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Für Einzelzeichenbegrenzer

Geteilte Zeichenfolge basierend auf einem Zeichenbegrenzer. Wenn Sie beispielsweise einen String "adsf+qwer+poui+fdgh"mit einem Trennzeichen teilen , "+"wird dies ausgegeben{"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;
}


Ausgabe

adsf
qwer
poui
fdgh

Sie kehren zurück vector<string>Ich denke, es wird Kopierkonstruktor nennen.
Mayur

2
Jede Referenz, die ich gesehen habe, zeigt, dass der Aufruf des Kopierkonstruktors in diesem Zusammenhang entfällt.
David gegeben

Bei "modernen" (C ++ 03?) Compilern halte ich dies für richtig. RVO- und / oder Verschiebungssemantik eliminieren den Kopierkonstruktor.
Kevin

Ich habe versucht, das Zeichen für ein Einzelzeichen-Trennzeichen zu verwenden. Wenn die Zeichenfolge in einem Trennzeichen endet (dh in einer leeren CSV-Spalte am Ende der Zeile), wird die leere Zeichenfolge nicht zurückgegeben. Es wird einfach eine Zeichenfolge weniger zurückgegeben. Zum Beispiel: 1,2,3,4 \ nA, B, C,
kounoupis

Ich habe auch das Zeichenfolgen-Trennzeichen ausprobiert. Wenn die Zeichenfolge mit einem Trennzeichen endet, wird das letzte Trennzeichen Teil der zuletzt extrahierten Zeichenfolge.
Kounoupis

19

Dieser Code trennt Zeilen vom Text und fügt alle zu einem Vektor hinzu.

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

Angerufen von:

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

es funktioniert super! Ich habe list.push_back (s) hinzugefügt; weil es fehlte.
Stoica Mircea

1
es fehlt der letzte Teil der Saite. Nach dem Ende der while-Schleife müssen wir den Rest von s als neues Token hinzufügen.
Whihathac

Ich habe das Codebeispiel bearbeitet, um den fehlenden push_back zu beheben.
Bund

1
Es wird schöner seinvector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

Mit strtok können Sie mehrere Zeichen als Trennzeichen übergeben. Ich wette, wenn Sie "> =" übergeben haben, wird Ihre Beispielzeichenfolge korrekt aufgeteilt (obwohl> und = als einzelne Trennzeichen gezählt werden).

BEARBEITEN Wenn Sie nicht c_str()zum Konvertieren von Zeichenfolge in Zeichen * verwenden möchten , können Sie substr und find_first_of zum Tokenisieren verwenden .

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
Vielen Dank. Ich möchte jedoch nur C ++ und keine C-Funktionen verwenden, strtok()da ich dafür ein char-Array anstelle eines Strings verwenden müsste.
TheCrazyProgrammer

2
@ TheCrazyProgrammer Also? Wenn eine C-Funktion das tut, was Sie brauchen, verwenden Sie sie. Dies ist keine Welt, in der C-Funktionen in C ++ nicht verfügbar sind (tatsächlich müssen sie es sein). .c_str()ist auch billig und einfach.
Qix - MONICA wurde

1
Die Überprüfung auf if (token! = Mystring) führt zu falschen Ergebnissen, wenn sich in Ihrer Zeichenfolge Elemente wiederholen. Ich habe Ihren Code verwendet, um eine Version zu erstellen, die dies nicht hat. Es gibt viele Änderungen, die die Antwort grundlegend ändern, also habe ich meine eigene Antwort geschrieben, anstatt sie zu bearbeiten. Überprüfen Sie es unten.
Amber Elferink

5

Hier ist meine Meinung dazu. Es behandelt die Randfälle und verwendet einen optionalen Parameter, um leere Einträge aus den Ergebnissen zu entfernen.

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

Beispiele

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

Dies sollte perfekt für Zeichenfolgen- (oder Einzelzeichen-) Trennzeichen funktionieren. Vergessen Sie nicht einzuschließen #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';
}

Die erste while-Schleife extrahiert ein Token mit dem ersten Zeichen des Zeichenfolgenbegrenzers. Die zweite while-Schleife überspringt den Rest des Trennzeichens und stoppt am Anfang des nächsten Tokens.


3

Ich würde verwenden boost::tokenizer. In der folgenden Dokumentation wird erläutert, wie eine geeignete Tokenizer-Funktion erstellt wird: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Hier ist eine, die für Ihren Fall funktioniert.

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
Vielen Dank. Ich möchte aber nur Standard-C ++ und keine Drittanbieter-Bibliothek wünschen.
TheCrazyProgrammer

@TheCrazyProgrammer: Okay, als ich "Standard C ++" las, dachte ich, dass dies keine nicht standardmäßigen Erweiterungen bedeutet, nicht, dass Sie keine Standards verwenden könnten, die Bibliotheken von Drittanbietern entsprechen.
Benjamin Lindley

3

Die Antwort ist bereits vorhanden, aber die ausgewählte Antwort verwendet die Löschfunktion, die sehr kostspielig ist. Denken Sie an eine sehr große Zeichenfolge (in MB). Deshalb benutze ich unten Funktion.

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

Ich habe das getestet und es funktioniert. Vielen Dank! Meiner Meinung nach ist dies die beste Antwort, da diese Lösung, wie der ursprüngliche Antwortende angibt, den Speicheraufwand reduziert und das Ergebnis bequem in einem Vektor gespeichert wird. (Repliziert die Python- string.split()Methode.)
Robbie Capps

2

Dies ist eine vollständige Methode, die die Zeichenfolge in ein beliebiges Trennzeichen aufteilt und einen Vektor der zerhackten Zeichenfolgen zurückgibt.

Es ist eine Adaption der Antwort von Ryanbwork. Seine Prüfung auf: if(token != mystring)führt jedoch zu falschen Ergebnissen, wenn sich in Ihrer Zeichenfolge Elemente wiederholen. Dies ist meine Lösung für dieses Problem.

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
So etwas while (true)ist normalerweise beängstigend in einem Code wie diesem zu sehen. Persönlich würde ich empfehlen, dies neu zu schreiben, damit der Vergleich mit std::string::npos(bzw. eine Prüfung gegen mystring.size()) das while (true)obsolet macht .
Joel Bodenmann

1

Wenn Sie die Zeichenfolge nicht ändern möchten (wie in der Antwort von Vincenzo Pii) und auch das letzte Token ausgeben möchten, können Sie diesen Ansatz verwenden:

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: Funktioniert nur, wenn die Länge der Zeichenfolgen nach dem Teilen gleich ist


Diese verwenden GCC-Erweiterung - Array mit variabler Länge.
user202729

0

Funktion:

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-Tests:

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
Bitte seien Sie genauer. Ihr Code wird nicht kompiliert. Siehe Deklaration von "i" und Komma anstelle eines Punktes.
jstuardo
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.