Przetwarzanie (dzielenie) łańcucha w C ++ przy użyciu ogranicznika łańcucha (standardowy C ++)


361

Analizuję ciąg w C ++ przy użyciu następujących czynności:

using namespace std;

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

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

Przetwarzanie za pomocą separatora pojedynczego znaku jest w porządku. Ale co, jeśli chcę użyć łańcucha jako separatora.

Przykład: Chcę podzielić:

scott>=tiger

z >=jako separatorem, abym mógł dostać szkota i tygrysa.

Odpowiedzi:


574

Możesz użyć tej std::string::find()funkcji, aby znaleźć pozycję ogranicznika łańcucha, a następnie użyć, std::string::substr()aby uzyskać token.

Przykład:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • find(const string& str, size_t pos = 0)Funkcja zwraca pozycję pierwszego wystąpienia strw ciągu, lub nposjeśli ciąg nie zostanie znaleziony.

  • substr(size_t pos = 0, size_t n = npos)Zwraca fragment obiektu, począwszy od położenia posi długości npos.


Jeśli masz wiele ograniczników, po wyodrębnieniu jednego tokena możesz go usunąć (w tym ogranicznik), aby kontynuować wyodrębnianie (jeśli chcesz zachować oryginalny ciąg, po prostu użyj s = s.substr(pos + delimiter.length());):

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

W ten sposób możesz łatwo zapętlić się, aby zdobyć każdy token.

Kompletny przykład

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;

Wynik:

scott
tiger
mushroom

66
Dla tych, którzy nie chcą modyfikować ciągu wejściowego, zróbsize_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
UWAGA: mushroomwyjścia poza pętlę, tj.s = mushroom
Don Larynx

1
Te próbki nie wyodrębniają ostatniego tokena z łańcucha. Próbka mojego wydobywania IpV4 z jednego ciągu: <code> size_t last = 0; size_t next = 0; int indeks = 0; while (indeks <4) {next = str.find (separator, ostatni); auto number = str.substr (ostatni, następny - ostatni); IPv4 [index ++] = atoi (number.c_str ()); ostatni = następny + 1; } </code>
rfog

2
@ hayk.mart Wystarczy pamiętać, że byłoby to następujące, musisz dodać 2, a nie 1 ze względu na rozmiar separatora, który wynosi 2 znaki :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; size_t last = 0; size_t next = 0; while ((next = s.find (delimiter, last))!! = std :: string :: npos) {std :: cout << s.substr (last, next-last) << std :: endl; ostatni = następny + 2; } std :: cout << s.substr (last) << std :: endl;
ervinbosenbacher

W celu uzyskania „tygrysa” użyj std::string token = s.substr(s.find(delimiter) + 1);, jeśli jesteś pewien, że istnieje (używam +1 w długości) ...
gsamaras

64

Ta metoda wykorzystuje std::string::findbez mutowania oryginalnego ciągu przez zapamiętywanie początku i końca poprzedniego tokenu podciągowego.

#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

Możesz użyć następnej funkcji do podzielenia łańcucha:

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 nie działa zgodnie z oczekiwaniami: split("abc","a")zwróci wektor lub pojedynczy ciąg, "bc"w którym myślę, że byłoby bardziej sensowne, gdyby zwrócił wektor elementów ["", "bc"]. Używanie str.split()w Pythonie intuicyjnie było dla mnie zwracać pusty ciąg w przypadku delimznalezienia go na początku lub na końcu, ale to tylko moja opinia. W każdym razie myślę, że należy o tym wspomnieć
kyriakosSt

1
Zdecydowanie zaleca usunięcie if (!token.empty()) problemu zapobiegającego wspomnianemu przez @kyriakosSt, a także innych problemów związanych z kolejnymi ogranicznikami.
Steve

1
Gdybym mógł, usunęłbym swoje poparcie, ale SO nie pozwala mi. Problem podniesiony przez @kyriakosSt jest problemem, a usunięcie if (!token.empty())go nie wydaje się wystarczające, aby go naprawić.
bhaller

1
@ bhaller ten fragment został zaprojektowany właśnie z myślą o pominięciu pustych fragmentów. Jeśli chcesz zachować puste, obawiam się, że musisz napisać kolejną podzieloną implementację. Uprzejmie sugerujemy opublikowanie go tutaj dla dobra społeczności.
Światosław

32

Dla ogranicznika ciągu

Podziel ciąg na podstawie ogranicznika ciągu . Takich jak dzielenie łańcucha "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"na podstawie ogranicznika łańcucha "-+", wyjście będzie{"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;
}


Wynik

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Dla separatora jednoznakowego

Podziel ciąg na podstawie ogranicznika znaków. Takich jak rozszczepiania łańcucha "adsf+qwer+poui+fdgh"z separatorem "+"pokaże na wyjściu{"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;
}


Wynik

adsf
qwer
poui
Fdgh

Wracasz vector<string>, myślę, że to wywoła konstruktora kopiowania.
Mayur

2
Każde odniesienie, które widziałem, pokazuje, że wywołanie konstruktora kopiowania jest w tym kontekście wyeliminowane.
David Biorąc pod uwagę

W przypadku „nowoczesnych” (C ++ 03?) Kompilatorów uważam, że jest to poprawne, semantyka RVO i / lub move wyeliminuje konstruktor kopii.
Kevin

Próbowałem tego dla ogranicznika jednoznakowego, a jeśli ciąg kończy się na separatorze (tj. Pusta kolumna csv na końcu wiersza), nie zwraca pustego ciągu. Zwraca tylko jeden ciąg mniej. Na przykład: 1,2,3,4 \ nA, B, C,
kounoupis

Próbowałem również tego dla ogranicznika ciągów, a jeśli ciąg kończy się na ograniczniku, ostatni ogranicznik staje się częścią ostatniego wyodrębnionego ciągu.
kounoupis

19

Ten kod dzieli linie z tekstu i dodaje wszystkich do wektora.

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

Nazwany przez:

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

działa świetnie! Dodałem list.push_back (s); ponieważ go brakowało.
Stoica Mircea

1
pomija ostatnią część łańcucha. Po zakończeniu pętli while musimy dodać resztę s jako nowy token.
whihathac

Dokonałem edycji próbki kodu, aby naprawić brakujące push_back.
próg

1
Będzie lepiejvector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok pozwala na przekazywanie wielu znaków jako ograniczników. Założę się, że jeśli podałeś "> =", twój przykładowy ciąg zostałby poprawnie podzielony (nawet jeśli> i = są liczone jako oddzielne separatory).

EDYCJA, jeśli nie chcesz używać c_str()do konwersji z łańcucha na char *, możesz użyć substr i find_first_of do tokenizacji .

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
Dzięki. Ale chcę używać tylko C ++, a nie żadnych funkcji C, strtok()ponieważ wymagałoby to użycia tablicy char zamiast łańcucha.
TheCrazyProgrammer

2
@TheCrazyProgrammer So? Jeśli funkcja C robi to, czego potrzebujesz, użyj jej. To nie jest świat, w którym funkcje C nie są dostępne w C ++ (w rzeczywistości muszą być). .c_str()jest również tanie i łatwe.
Qix - MONICA MISTREATEDED

1
Sprawdzanie, czy if (token! = Mystring) daje nieprawidłowe wyniki, jeśli w łańcuchu występują powtarzające się elementy. Użyłem twojego kodu, aby stworzyć wersję, która go nie ma. Ma wiele zmian, które zasadniczo zmieniają odpowiedź, więc napisałem własną odpowiedź zamiast edycji. Sprawdź to poniżej.
Amber Elferink

5

Oto moje zdanie na ten temat. Obsługuje przypadki krawędzi i przyjmuje opcjonalny parametr, aby usunąć puste wpisy z wyników.

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

Przykłady

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

Powinno to działać idealnie dla ograniczników ciągów (lub pojedynczych znaków). Nie zapomnij dołączyć #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';
}

Pierwsza pętla while wyodrębnia token za pomocą pierwszego znaku ogranicznika łańcucha. Druga pętla while pomija resztę separatora i zatrzymuje się na początku następnego tokena.


3

Chciałbym użyć boost::tokenizer. Oto dokumentacja wyjaśniająca, jak wykonać odpowiednią funkcję tokenizera: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Oto taki, który działa w twoim przypadku.

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
Dzięki. Ale chcę życzyć tylko standardowego C ++, a nie biblioteki innej firmy.
TheCrazyProgrammer,

@TheCrazyProgrammer: Okay, kiedy przeczytałem „Standard C ++”, pomyślałem, że to oznacza brak niestandardowych rozszerzeń, nie że nie można używać bibliotek stron trzecich zgodnych ze standardami.
Benjamin Lindley,

3

Odpowiedź już tam jest, ale wybrana odpowiedź używa funkcji wymazywania, która jest bardzo kosztowna, pomyśl o bardzo dużym ciągu znaków (w MB). Dlatego używam funkcji poniżej.

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

Przetestowałem to i działa. Dzięki! Moim zdaniem jest to najlepsza odpowiedź, ponieważ jak stwierdza pierwotna odpowiedź, to rozwiązanie zmniejsza obciążenie pamięci, a wynik jest wygodnie przechowywany w wektorze. (replikuje string.split()metodę Python .)
Robbie Capps

2

Jest to kompletna metoda, która dzieli ciąg na dowolny ogranicznik i zwraca wektor pociętych ciągów.

Jest to adaptacja odpowiedzi od ryanbwork. Jednak jego sprawdzanie: if(token != mystring)daje nieprawidłowe wyniki, jeśli masz powtarzające się elementy w swoim ciągu. To jest moje rozwiązanie tego problemu.

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
Coś takiego while (true)jest zwykle przerażające, gdy widzi się taki fragment kodu. Osobiście polecam przepisać to tak, aby porównanie std::string::npos(lub odpowiednio porównanie z mystring.size()) sprawiło, że while (true)przestarzała.
Joel Bodenmann,

1

Jeśli nie chcesz modyfikować łańcucha (jak w odpowiedzi Vincenzo Pii) i chcesz również wypisać ostatni token, możesz użyć tego podejścia:

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: Działa tylko wtedy, gdy długości ciągów po podziale są równe


To użycie rozszerzenia GCC - tablica o zmiennej długości.
user202729,

0

Funkcjonować:

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

Testy jednostkowe:

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
Proszę być bardziej dokładnym. Twój kod się nie skompiluje. Zobacz deklarację „i” i przecinek zamiast kropki.
jstuardo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.