Jak iterować po słowach ciągu?

2985

Próbuję iterować słowa łańcucha.

Można założyć, że ciąg składa się ze słów oddzielonych spacją.

Zauważ, że nie interesują mnie funkcje łańcucha C ani tego rodzaju manipulacja / dostęp do znaków. W swojej odpowiedzi prosimy również o pierwszeństwo elegancji przed wydajnością.

Najlepszym rozwiązaniem, jakie mam teraz, jest:

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

Czy jest na to bardziej elegancki sposób?

Ashwin Nanjappa
źródło
617
Koleś ... Elegancja to po prostu fantazyjny sposób na powiedzenie „wydajność-która-wygląda-ładnie” w mojej książce. Nie wahaj się korzystać z funkcji C i szybkich metod, aby cokolwiek osiągnąć tylko dlatego, że nie jest to zawarte w szablonie;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon
21
@Eduardo: to też źle ... musisz sprawdzić, czy próbujesz przesłać strumieniowo inną wartość i użyć tej wartości, tj.string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Różne opcje w C ++ to zrobić domyślnie: cplusplus.com/faq/sequences/strings/split
hB0
14
Elegancja to coś więcej niż tylko całkiem wydajna praca. Eleganckie atrybuty obejmują małą liczbę linii i wysoką czytelność. IMHO Elegance to nie wskaźnik wydajności, ale łatwość konserwacji.
Matt

Odpowiedzi:

1368

Oto, co warto, oto inny sposób na wyodrębnienie tokenów z ciągu wejściowego, opierając się tylko na standardowych urządzeniach bibliotecznych. To przykład siły i elegancji stojących za stylistyką 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"));
}

Zamiast kopiować wyodrębnione tokeny do strumienia wyjściowego, można wstawić je do kontenera, używając tego samego copyalgorytmu ogólnego .

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

... lub utwórz vectorbezpośrednio:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
źródło
164
Czy można dla tego określić ogranicznik? Jak na przykład dzielenie przecinków?
l3dx,
15
@Jathanathan: \ n nie jest ogranicznikiem w tym przypadku, jest to ogranicznik dla wyjścia do cout.
huy
772
Jest to słabe rozwiązanie, ponieważ nie wymaga żadnego innego ogranicznika, dlatego nie jest skalowalne i nie jest możliwe do utrzymania.
HelloWorld,
37
W rzeczywistości może to działać dobrze z innymi ogranicznikami (choć wykonywanie niektórych jest nieco brzydkie). Tworzysz aspekt ctype, który klasyfikuje pożądane ograniczniki jako białe znaki, tworzysz ustawienia narodowe zawierające ten aspekt, a następnie nasycasz strumień znaków tym miejscem przed wyodrębnieniem ciągów.
Jerry Coffin,
53
@Kinderchocolate „Można założyć, że ciąg składa się ze słów oddzielonych spacjami” - Hmm, nie brzmi jak złe rozwiązanie problemu. „nieskalowalne i nie do utrzymania” - Hah, fajne.
Christian Rau,
2425

Używam tego do dzielenia łańcucha przez separator. Pierwszy umieszcza wyniki we wstępnie skonstruowanym wektorze, drugi zwraca nowy wektor.

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

Zwróć uwagę, że to rozwiązanie nie pomija pustych tokenów, więc poniższe znajdą 4 przedmioty, z których jeden jest pusty:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran
źródło
86
Aby uniknąć pomijania pustych tokenów, empty()sprawdź:if (!item.empty()) elems.push_back(item)
0x499602D2
11
A co z delimem zawiera dwa znaki jak ->?
herohuyongtao,
7
@herohuyongtao, to rozwiązanie działa tylko dla ograniczników pojedynczego znaku.
Evan Teran
4
@JeshwanthKumarNK, nie jest to konieczne, ale pozwala robić rzeczy, takie jak przekazywanie wyniku bezpośrednio do funkcji takiej jak ta: f(split(s, d, v))przy jednoczesnym korzystaniu z wstępnie przydzielonego, vectorjeśli chcesz.
Evan Teran
8
Zastrzeżenie: split („jeden: dwa :: trzy”, „:”) i split („jeden: dwa :: trzy:”, „:”) zwracają tę samą wartość.
dshin
834

Możliwym rozwiązaniem wykorzystującym Boost może być:

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

Takie podejście może być nawet szybsze niż stringstreampodejście. A ponieważ jest to ogólna funkcja szablonu, może być używana do dzielenia innych typów ciągów (wchar itp. Lub UTF-8) przy użyciu wszelkiego rodzaju ograniczników.

Szczegółowe informacje można znaleźć w dokumentacji .

ididak
źródło
35
Prędkość nie ma tu znaczenia, ponieważ oba te przypadki są znacznie wolniejsze niż funkcja podobna do strtok.
Tom
45
A dla tych, którzy jeszcze nie mają doładowania ... bcp kopiuje w tym celu ponad 1000 plików :)
Roman Starkov
12
Ostrzeżenie, jeśli podano pusty ciąg („”), ta metoda zwraca wektor zawierający ciąg „”. Więc dodaj „split (! String_to_split.empty ())” przed podziałem.
Offirmo,
29
@Ian Embedded programiści nie wszyscy używają boost.
ACK_stoverflow
31
jako uzupełnienie: używam boost tylko wtedy, gdy muszę, zwykle wolę dodawać do własnej biblioteki kodu, który jest samodzielny i przenośny, dzięki czemu mogę osiągnąć mały, precyzyjny, konkretny kod, który osiąga dany cel. W ten sposób kod jest niepubliczny, wydajny, trywialny i przenośny. Boost ma swoje miejsce, ale sugerowałbym, że to trochę przesada w tokenizowaniu łańcuchów: nie kazałbyś całego domu przetransportować do firmy inżynierskiej, aby wbić nowy gwóźdź wbity w ścianę, aby powiesić zdjęcie ... mogą to zrobić bardzo dobrze, ale zalety zdecydowanie przewyższają zalety.
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;
}
kev
źródło
12
Można również podzielić na innych ograniczników jeśli używasz getlinew whilestan np rozłamu przecinkami użytku while(getline(ss, buff, ',')).
Ali,
181

Dla tych, z którymi nie najlepiej jest poświęcić całą wydajność dla rozmiaru kodu i postrzegać „efektywny” jako rodzaj elegancji, poniższe elementy powinny trafić w dobre miejsce (i myślę, że klasa kontenera szablonów jest niesamowicie eleganckim dodatkiem):

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

Zwykle wybieram użycie std::vector<std::string>typów jako mojego drugiego parametru ( ContainerT) ... ale list<>jest o wiele szybszy niż w vector<>przypadku, gdy bezpośredni dostęp nie jest potrzebny, a nawet możesz stworzyć własną klasę łańcuchów i użyć czegoś takiego, std::list<subString>gdzie subStringnie robi żadnych kopii z niewiarygodną prędkością wzrasta.

Jest ponad dwukrotnie szybszy niż najszybszy tokenizuj na tej stronie i prawie 5 razy szybszy niż niektóre inne. Również dzięki idealnym typom parametrów możesz wyeliminować wszystkie kopie ciągów i list, aby zwiększyć prędkość.

Ponadto nie powoduje (wyjątkowo nieefektywnego) zwrotu wyniku, ale raczej przekazuje tokeny jako odniesienie, co pozwala również na budowanie tokenów przy użyciu wielu wywołań, jeśli chcesz.

Wreszcie pozwala określić, czy przycinać puste tokeny z wyników za pomocą ostatniego opcjonalnego parametru.

Wszystko czego potrzebuje to std::string... reszta jest opcjonalna. Nie używa strumieni ani biblioteki doładowania, ale jest wystarczająco elastyczny, aby naturalnie akceptować niektóre z tych obcych typów.

Marius
źródło
5
Jestem tego fanem, ale dla g ++ (i prawdopodobnie dobrej praktyki) każdy, kto tego użyje, będzie chciał typedefs i typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; nazwy pseudonimów : Następnie odpowiednio zastąpić typ_typu i typ_rozmiaru.
aws
11
Dla tych z nas, dla których szablon i pierwszy komentarz są całkowicie obce, użyteczne byłoby użycie cmplete z wymaganymi załącznikami.
Wes Miller,
3
Ach cóż, rozgryzłem to. Wstawiam wiersze C ++ z komentarza aws do treści funkcji tokenize (), następnie edytuję linie tokens.push_back (), aby zmienić typ ContainerT :: value_type na wartość ValueType i zmieniłem (ContainerT :: typ_wartości :: typ_rozmiaru) na ( SizeType). Naprawiono bity, o których gyczyło g ++. Po prostu wywołaj to jako tokenize (some_string, some_vector);
Wes Miller,
2
Oprócz przeprowadzenia kilku testów wydajności na przykładowych danych, przede wszystkim zredukowałem je do jak najmniejszej liczby instrukcji, a także jak najmniejszej liczby kopii pamięci włączonych dzięki użyciu klasy podłańcucha, która odwołuje się tylko do przesunięć / długości w innych ciągach. (Rzuciłem własne, ale są też inne implementacje). Niestety niewiele można zrobić, aby to poprawić, ale możliwe były przyrostowe wzrosty.
Marius
3
To jest poprawne wyjście dla kiedy trimEmpty = true. Pamiętaj, że "abo"w tej odpowiedzi nie jest ogranicznikiem, ale lista znaków ogranicznika. Łatwo byłoby zmodyfikować go tak, aby przyjmował pojedynczy ciąg znaków separatora (myślę, że str.find_first_ofpowinienem zmienić str.find_first, ale mogę się mylić ... nie mogę przetestować)
Marius
158

Oto inne rozwiązanie. Jest kompaktowy i stosunkowo wydajny:

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

Może być łatwo szablonowany do obsługi separatorów strun, szerokich strun itp.

Zauważ, że dzielenie ""powoduje pojedynczy pusty ciąg, a dzielenie ","(np. Sep) powoduje dwa puste ciągi.

Można go również łatwo rozszerzyć, aby pomijać puste tokeny:

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

Jeśli pożądany jest podział łańcucha na wiele ograniczników podczas pomijania pustych tokenów, można użyć tej wersji:

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;
}
Alec Thomas
źródło
10
Pierwsza wersja jest prosta i doskonale wykonuje pracę. Jedyną zmianą, którą wprowadziłbym, byłoby zwrócenie wyniku bezpośrednio, zamiast przekazywania go jako parametru.
gregschlom,
2
Dane wyjściowe są przekazywane jako parametr wydajności. Gdyby wynik został zwrócony, wymagałaby albo kopii wektora, albo przydziału stosu, który musiałby zostać zwolniony.
Alec Thomas
2
Niewielki dodatek do mojego powyższego komentarza: ta funkcja może zwrócić wektor bez kary, jeśli używa się semantyki ruchu C ++ 11.
Alec Thomas
7
@AlecThomas: Nawet przed C ++ 11 większość kompilatorów nie zoptymalizowałaby kopii zwrotnej przez NRVO? (W każdym razie +1; bardzo zwięzłe)
Marcelo Cantos
11
Spośród wszystkich odpowiedzi wydaje się to jedną z najbardziej atrakcyjnych i elastycznych. Wraz z getline z separatorem, choć jest to mniej oczywiste rozwiązanie. Czy standard c ++ 11 nie ma nic do tego? Czy c ++ 11 obsługuje obecnie karty perforowane?
Spacen Jasset,
123

To mój ulubiony sposób na iterację po łańcuchu. Możesz zrobić, co chcesz na słowo.

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

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
gnomów
źródło
Czy można zadeklarować wordjako char?
abatishchev
Niestety abatishchev, C ++ nie jest moją mocną stroną. Ale wyobrażam sobie, że nietrudno byłoby dodać wewnętrzną pętlę do każdego znaku w każdym słowie. Ale teraz uważam, że obecna pętla zależy od spacji dla separacji słów. Chyba że wiesz, że między każdą spacją jest tylko jedna postać, w którym to przypadku możesz po prostu rzucić „słowo” na znak… przepraszam, nie mogę ci pomóc, chciałem odświeżyć mój C ++
gnomy
11
jeśli zadeklarujesz słowo jako znak, będzie iterować po każdym znaku innym niż spacja. Wystarczy wypróbować:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Jest to podobne do pytania Przepełnienie stosu Jak tokenizować ciąg w 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;
    }
}
Ferruccio
źródło
Czy to zmaterializuje kopię wszystkich tokenów, czy tylko zachowuje pozycję początkową i końcową bieżącego tokena?
einpoklum
66

Podobają mi się następujące, ponieważ umieszcza wyniki w wektorze, obsługuje ciąg jako separator i daje kontrolę nad utrzymywaniem pustych wartości. Ale to nie wygląda tak dobrze.

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

Oczywiście, Boost ma taki, split()który działa częściowo tak. A jeśli przez „białą spację”, naprawdę masz na myśli dowolny rodzaj białej spacji, użycie podziału Boosta is_any_of()działa świetnie.

Shadow2531
źródło
Wreszcie rozwiązanie, które poprawnie obsługuje puste tokeny po obu stronach łańcucha
fmuecke
53

STL nie ma już takiej metody dostępnej.

Możesz jednak użyć strtok()funkcji C za pomocą std::string::c_str()członka lub możesz napisać własną. Oto przykładowy kod, który znalazłem po szybkim wyszukiwaniu w Google ( „Podział ciągu 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);
    }
}

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

Jeśli masz pytania dotyczące próbki kodu, zostaw komentarz, a ja ci wyjaśnię.

I tylko dlatego, że nie implementuje on typedefzwanego iteratora lub przeciążenia, <<operator nie oznacza, że ​​jest to zły kod. Często używam funkcji C. Na przykład, printfi scanfoba są szybsze niż std::cini std::cout(znacząco), fopenskładnia jest o wiele bardziej przyjazna dla typów binarnych, a także mają tendencję do tworzenia mniejszych plików EXE.

Nie daj się sprzedać w ramach oferty „Elegancja ponad wydajność” .

19302
źródło
Jestem świadomy funkcji łańcucha C i zdaję sobie również sprawę z problemów z wydajnością (obie te kwestie zauważyłem w moim pytaniu). Jednak w przypadku tego konkretnego pytania szukam eleganckiego rozwiązania w języku C ++.
Ashwin Nanjappa,
11
@Nelson LaQuet: Niech zgadnę: Ponieważ strtok nie jest ponownie wysyłany?
paercebal,
40
@Nelson nigdy nie przekazuje string.c_str () do strtok! strtok niszczy łańcuch wejściowy (wstawia znaki „\ 0”, aby zastąpić każdy separator foudn), a c_str () zwraca ciąg niemodyfikowalny.
Evan Teran,
3
@Nelson: Tablica musi mieć rozmiar str.size () + 1 w ostatnim komentarzu. Ale zgadzam się z twoją tezą, że głupio jest unikać funkcji C z powodów „estetycznych”.
j_random_hacker
2
@paulm: Nie, powolność strumieni C ++ jest spowodowana aspektami. Są nadal wolniejsze niż funkcje stdio.h, nawet gdy synchronizacja jest wyłączona (i na strumieniach strumieniowych, które nie mogą synchronizować).
Ben Voigt,
42

Oto funkcja podziału, która:

  • jest ogólny
  • używa standardowego C ++ (bez wzmocnienia)
  • akceptuje wiele ograniczników
  • ignoruje puste tokeny (można je łatwo zmienić)

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

Przykładowe użycie:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
źródło
Zapomniałeś dodać do listy użycia: „niezwykle nieefektywny”
Xander Tulip
1
@XanderTulip, czy możesz być bardziej konstruktywny i wyjaśnić, jak i dlaczego?
Marco M.
3
@XanderTulip: Zakładam, że masz na myśli, że zwracasz wektor według wartości. Zajmie się tym optymalizacja wartości zwrotu (RVO, google it). Również w C ++ 11 możesz powrócić przez referencję przeniesienia.
Joseph Garvin
3
Można to faktycznie zoptymalizować dalej: zamiast .push_back (str.substr (...)) można użyć .emplace_back (str, start, pos - start). W ten sposób obiekt łańcucha jest konstruowany w kontenerze, dzięki czemu unikamy operacji przenoszenia + innych shenaniganów wykonanych przez funkcję .substr.
Mihai Bişog,
@zoopp tak. Dobry pomysł. Kiedy to napisałem, VS10 nie posiadał wsparciapracownika. Zaktualizuję swoją odpowiedź. Dzięki
Marco M.
36

Mam 2-liniowe rozwiązanie tego problemu:

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;

Następnie zamiast drukowania możesz umieścić go w wektorze.

rhomu
źródło
35

Kolejny elastyczny i szybki sposób

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

Aby użyć go z wektorem ciągów (Edytuj: Ponieważ ktoś wskazał, aby nie dziedziczyć klas 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");

Otóż ​​to! A to tylko jeden ze sposobów korzystania z tokenizera, na przykład liczenie słów:

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

Ograniczona wyobraźnią;)

Robert
źródło
Miły. W odniesieniu do Appenderuwagi „Dlaczego nie powinniśmy dziedziczyć klasy po klasach STL?”
Andreas Spindler,
32

Oto proste rozwiązanie, które korzysta tylko ze standardowej biblioteki wyrażeń regularnych

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

Argument regex pozwala na sprawdzenie wielu argumentów (spacje, przecinki itp.)

Zazwyczaj zaznaczam tylko podział na spacje i przecinki, więc mam również tę domyślną funkcję:

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

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

    return Tokenize( str, re );
}

Do "[\\s,]+"sprawdza spacji ( \\s) i przecinków ( ,).

Uwaga: jeśli chcesz podzielić wstringzamiast string,

  • zmień wszystko std::regexnastd::wregex
  • zmień wszystko sregex_token_iteratornawsregex_token_iterator

Uwaga: w zależności od kompilatora możesz również chcieć wziąć argument ciągu znaków przez odniesienie.

dk123
źródło
To byłaby moja ulubiona odpowiedź, ale std :: regex jest uszkodzony w GCC 4.8. Powiedzieli, że zaimplementowali go poprawnie w GCC 4.9. Nadal daję ci moje +1
mchiasson
1
Jest to mój ulubiony z drobnymi zmianami: wektor zwrócił jako odniesienie, jak powiedziałeś, a także argumenty „str” i „regex” przekazane przez odniesienia. dzięki.
QuantumKarl
1
Surowe ciągi znaków są bardzo przydatne podczas radzenia sobie z wzorcami wyrażeń regularnych. W ten sposób nie musisz używać sekwencji ucieczki ... Możesz po prostu użyć R"([\s,]+)".
Sam
26

Używanie tego, std::stringstreamco masz, działa doskonale i rób dokładnie to, co chciałeś. Jeśli jednak szukasz innego sposobu robienia rzeczy, możesz użyć std::find()/ std::find_first_of()i std::string::substr().

Oto przykład:

#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;
}
KTC
źródło
Działa to tylko w przypadku ograniczników jednoznakowych. Prosta zmiana umożliwia pracę z wieloma znakami:prev_pos = pos += delimiter.length();
David Doria,
25

Jeśli chcesz użyć wzmocnienia, ale chcesz użyć całego łańcucha jako separatora (zamiast pojedynczych znaków, jak w większości wcześniej proponowanych rozwiązań), możesz użyć boost_split_iterator.

Przykładowy kod zawierający wygodny szablon:

#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;
}
Zerm
źródło
20

Oto rozwiązanie wyrażenia regularnego, które wykorzystuje tylko standardową bibliotekę wyrażeń regularnych. (Jestem trochę zardzewiały, więc może być kilka błędów składniowych, ale to przynajmniej ogólny pomysł)

#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
}
AJMansfield
źródło
Podobne odpowiedzi z być może lepszym wyrażeniem regularnym: tutaj i tutaj .
nobar
20

Istnieje funkcja o nazwie 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;
}
Pratik Deoghare
źródło
3
strtokpochodzi ze standardowej biblioteki C, a nie C ++. Nie jest bezpiecznie używać w programach wielowątkowych. Zmienia ciąg wejściowy.
Kevin Panko
13
Ponieważ przechowuje wskaźnik char z pierwszego wywołania w zmiennej statycznej, tak że przy kolejnych wywołaniach, gdy NULL jest przekazywany, pamięta, który wskaźnik powinien zostać użyty. Jeśli drugi wątek zadzwoni, strtokgdy inny wątek wciąż przetwarza, ten wskaźnik char zostanie zastąpiony, a następnie oba wątki będą miały niepoprawne wyniki. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
Jak wspomniano wcześniej strtok jest niebezpieczna, a nawet C strtok_r jest zalecane
systemsfault
4
Strtok_r może być używany, jeśli znajdujesz się w sekcji kodu, do której można uzyskać dostęp. jest to tylko rozwiązanie wszystkich wyżej wymienionych, który nie jest „szum linii”, i jest to dowodem na to, co dokładnie jest nie tak z C ++
Erik Aronesty
Zaktualizowano, aby nie było żadnych zastrzeżeń ze względu na bezpieczeństwo wątków ze strony C ++ winks.
Erik Aronesty
17

Strumień ciągu może być wygodny, jeśli trzeba parsować ciąg za pomocą symboli spacji:

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, ';')
lukmac
źródło
14

Do tej pory korzystałem z tego w Boost , ale potrzebowałem czegoś, co nie zależy od tego, więc doszedłem do tego:

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

Dobrą rzeczą jest to, separatorsże możesz przekazać więcej niż jedną postać.

Goran
źródło
13

Rzuciłem własną za pomocą strtok i użyłem boosta do podzielenia łańcucha. Najlepszą metodą, jaką znalazłem, jest biblioteka C ++ String Toolkit Library . Jest niezwykle elastyczny i szybki.

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

Zestaw narzędzi ma znacznie większą elastyczność niż pokazuje ten prosty przykład, ale jego użyteczność w parsowaniu łańcucha na użyteczne elementy jest niesamowita.

DannyK
źródło
13

Krótki i elegancki

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

może używać dowolnego łańcucha jako separatora, może być również używany z danymi binarnymi (std :: string obsługuje dane binarne, w tym wartości null)

za pomocą:

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

wynik:

this
is
!example!string
1438233
źródło
1
Podoba mi się to rozwiązanie, ponieważ pozwala separatorowi być ciągiem znaków, a nie znakiem, jednak modyfikuje ciąg w miejscu, więc wymusza tworzenie kopii oryginalnego ciągu znaków.
Alessandro Teruzzi
11

Zrobiłem to, ponieważ potrzebowałem łatwego sposobu na podzielenie łańcuchów i łańcuchów opartych na c ... Mam nadzieję, że ktoś inny również może się przydać. Nie zależy też od tokenów i możesz używać pól jako ograniczników, co jest kolejnym kluczem, którego potrzebowałem.

Jestem pewien, że można wprowadzić ulepszenia, aby jeszcze bardziej poprawić jego elegancję i proszę zrobić to za wszelką cenę

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

Przykłady:

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

Wyjdzie:

To
jest przykład 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;
}

Aby zachować puste wpisy (domyślnie puste zostaną wykluczone):

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

Celem było uczynienie go podobnym do metody Split () C #, w której dzielenie łańcucha jest tak proste, jak:

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

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

Mam nadzieję, że ktoś inny uzna to za równie przydatne, co ja.

Steve Dell
źródło
10

A co z tym:

#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;
}
gibbz
źródło
To najlepsza odpowiedź tutaj, jeśli chcesz podzielić tylko jeden znak separatora. Pierwotne pytanie chciało się jednak podzielić na białe znaki, co oznacza dowolną kombinację jednej lub więcej kolejnych spacji lub tabulatorów. Właściwie odpowiedziałeś na stackoverflow.com/questions/53849
Oktalist
10

Ta odpowiedź pobiera ciąg znaków i umieszcza go w wektorze ciągów. Korzysta z biblioteki doładowań.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
NL628
źródło
9

Oto inny sposób na zrobienie tego ...

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);
  }
}
user246110
źródło
9

Lubię używać do tego zadania metod boost / regex, ponieważ zapewniają one maksymalną elastyczność przy określaniu kryteriów podziału.

#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;
}
Marty B.
źródło
9

Ostatnio musiałem podzielić słowo w wielbłądach na słowa podrzędne. Nie ma żadnych ograniczników, tylko górne znaki.

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

Na przykład dzieli to „AQueryTrades” na „A”, „Query” i „Trades”. Funkcja działa z wąskimi i szerokimi łańcuchami. Ponieważ szanuje obecną lokalizację, dzieli „RaumfahrtÜberwachungsVerordnung” na „Raumfahrt”, „Überwachungs” i „Verordnung”.

Uwaga std::upperpowinna być naprawdę przekazana jako argument szablonu funkcji. Wtedy bardziej uogólnione z tej funkcji można podzielić na ograniczniki, takie jak ",", ";"lub " "też.

Andreas Spindler
źródło
2
Były 2 obroty. To miłe. Wygląda na to, że mój angielski miał w dużym stopniu „niemiecki”. Jednak rewizjonista nie naprawił dwóch drobnych błędów, być może dlatego, że i tak były oczywiste: std::isuppermożna je uznać za argument, a nie std::upper. Po drugie umieść typenameprzed 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;
}
san45
źródło
9

Korzystanie std::string_viewi range-v3biblioteka Erica Nieblera :

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

Używając forpętli zakresu zamiast ranges::for_eachalgorytmu:

#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";
    }
}
Porsche9II
źródło
Tak, zakres bazowania wygląda lepiej - zgadzam się
Porsche9II