Pytanie Najbardziej elegancki sposób na iterację słów ciągu [zamknięty]


Jaki jest najbardziej elegancki sposób na powtórzenie słów ciągu? Można zakładać, że ciąg składa się ze słów oddzielonych białymi znakami.

Zauważ, że nie interesują mnie funkcje ciągów C lub tego rodzaju manipulacja / dostęp do postaci. Proszę również nadać priorytet elegancji nad skutecznością w odpowiedzi.

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

2638


pochodzenie


Dude ... Elegance to po prostu fantastyczny sposób na powiedzenie "sprawność, która wygląda całkiem" w mojej książce. Nie wahaj się korzystać z funkcji C i szybkich metod, aby osiągnąć wszystko tylko dlatego, że nie zawiera się w szablonie;) - nlaq
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; } - pyon
@Eduardo: to też jest złe ... musisz przetestować iss pomiędzy próbowaniem strumieniowania innej wartości i użyciem tej wartości, tj. string sub; while (iss >> sub) cout << "Substring: " << sub << '\n'; - Tony Delroy
Różne opcje w C ++, aby to zrobić domyślnie: cplusplus.com/faq/sequences/strings/split - hB0
Więcej elegancji niż tylko piękna wydajność. Eleganckie atrybuty obejmują niską liczbę linii i wysoką czytelność. IMHO Elegance nie jest wskaźnikiem wydajności, ale łatwości konserwacji. - Matt


Odpowiedzi:


Jeśli chodzi o to, co warto, oto inny sposób wyodrębniania tokenów z łańcucha wejściowego, opierający się tylko na standardowych obiektach biblioteki. To przykład mocy i elegancji stojącej za designem 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 je wstawić do kontenera, używając tego samego generycznego copy algorytm.

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

... lub utwórz vector bezpośrednio:

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

1189



Czy jest możliwe określenie dla tego ogranicznika? Jak na przykład podział na przecinki? - l3dx
@ Jonathan: \ n nie jest ogranicznikiem w tym przypadku, jest to deliminer dla wysyłania do cout. - huy
Jest to kiepskie rozwiązanie, ponieważ nie zajmuje żadnego innego ogranicznika, dlatego nie jest skalowalne i nie można go konserwować. - SmallChess
Właściwie to mogą działa dobrze z innymi ogranicznikami (choć niektóre z nich są nieco brzydkie). Tworzysz aspekt ctype, który klasyfikuje żądane separatory jako białe znaki, tworzy ustawienia regionalne zawierające ten aspekt, a następnie dodaje łańcuch znaków do tych ustawień narodowych przed wyodrębnianiem łańcuchów. - Jerry Coffin
@ Kinderchocolate "Można zakładać, że ciąg składa się ze słów oddzielonych białymi znakami"- Hmm, nie brzmi jak marne rozwiązanie problemu. "nie skalowalne i nie do utrzymania" - Hah, niezły. - Christian Rau


Używam tego, aby podzielić ciąg znaków za pomocą ogranicznika. Pierwszy umieszcza wyniki we wstępnie skonstruowanym wektorze, a 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::stringstream ss(s);
    std::string item;
    while (std::getline(ss, 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;
}

Zauważ, że to rozwiązanie nie pomija pustych tokenów, więc poniżej znajdziesz 4 elementy, z których jedna jest pusta:

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

2308



Aby uniknąć pomijania pustych tokenów, zrób empty() czek: if (!item.empty()) elems.push_back(item) - 0x499602D2
Co powiesz na delim zawiera dwa znaki jako ->? - herohuyongtao
@herohuyongtao, to rozwiązanie działa tylko w przypadku ograniczników pojedynczych znaków. - Evan Teran
@JeshwanthKumarNK, to nie jest konieczne, ale pozwala ci na takie rzeczy, jak przekazanie wyniku bezpośrednio do funkcji podobnej do tej: f(split(s, d, v)) przy jednoczesnym korzystaniu z wcześniej przydzielonego vector Jeśli lubisz. - Evan Teran
Zastrzeżenie: split ("jeden: dwa :: trzy", ":") i split ("jeden: dwa :: trzy:", ":") zwracają tę samą wartość. - dshin


Możliwym rozwiązaniem przy użyciu 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ż stringstream podejście. A ponieważ jest to ogólna funkcja szablonu, może być używana do dzielenia innych typów łańcuchów (wchar, itp. Lub UTF-8) przy użyciu wszelkiego rodzaju ograniczników.

Zobacz dokumentacja dla szczegółów.


794



Prędkość nie ma tutaj znaczenia, ponieważ oba te przypadki są znacznie wolniejsze niż funkcja podobna do strtok. - Tom
A dla tych, którzy jeszcze nie mają doładowania ... bcp kopiuje ponad 1000 plików do tego :) - Roman Starkov
strtok to pułapka. wątek jest niebezpieczny. - tuxSlayer
@Ian Embedded Developers nie wszyscy używają boost. - ACK_stoverflow
jako dodatek: używam boost tylko wtedy, gdy muszę, zazwyczaj wolę dodawać do mojej własnej biblioteki kodu, który jest samodzielny i przenośny, dzięki czemu mogę osiągnąć mały precyzyjny kod, który osiąga określony cel. W ten sposób kod jest niepubliczny, wydajny, banalny i przenośny. Boost ma swoje miejsce, ale sugerowałbym, że jest to trochę przesadzone w przypadku tokenizacji ciągów: nie musiałbyś przetransportować całego domu do firmy inżynieryjnej, aby uzyskać nowy gwóźdź wbity w ścianę, aby powiesić zdjęcie ... mogą to zrobić bardzo dobrze, ale prosie zdecydowanie przewyższają minusy. - GMasucci


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

321



szkoda, że ​​dzieli się tylko na przestrzenie ' '... - Offirmo


Dla tych, z którymi nie jest dobrze, aby poświęcić całą efektywność rozmiaru kodu i zobaczyć "wydajne" jako rodzaj elegancji, następujące rzeczy powinny trafić w słodkie 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 używam std::vector<std::string> typy jako mój drugi parametr (ContainerT)... ale list<> jest o wiele szybszy niż vector<> gdy dostęp bezpośredni nie jest potrzebny, a nawet można utworzyć własną klasę ciągów i użyć czegoś podobnego std::list<subString> gdzie subString nie wykonuje żadnych kopii dla niewiarygodnego zwiększenia prędkości.

Jest ponad dwukrotnie szybszy niż najszybszy tokenize na tej stronie i prawie 5 razy szybszy niż inne. Również z idealnymi typami parametrów można wyeliminować wszystkie ciągi i kopie list w celu zwiększenia prędkości.

Dodatkowo nie robi to (bardzo nieefektywnego) zwrotu wyniku, ale raczej przekazuje tokeny jako odniesienie, co pozwala również na budowanie tokenów za pomocą wielu połączeń, jeśli sobie tego życzysz.

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 boost, ale jest wystarczająco elastyczny, aby móc zaakceptować niektóre z tych obcych typów w sposób naturalny.


168



Jestem całkiem fanem tego, ale dla g ++ (i prawdopodobnie dobrej praktyki) każdy, kto tego używa, będzie chciał literówek i nazw maszynowych: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType;  Następnie, aby odpowiednio zastąpić wartości typ_wartości i size_types. - aws
Dla tych z nas, dla których szablony i pierwszy komentarz są całkowicie obce, przykład użycia cmplete z wymaganymi załącznikami byłby piękny. - Wes Miller
No cóż, odkryłem to. Umieściłem linie C ++ od komentarza aws wewnątrz ciała funkcji tokenize (), a następnie edytowałem linie tokens.push_back (), aby zmienić wartość typu ContainerT :: value_to na ValueType i zmieniono (ContainerT :: value_type :: size_type) na ( SizeType). Naprawiono bity g ++. Po prostu wywołaj to jako tokenize (some_string, some_vector); - Wes Miller
Poza przeprowadzeniem kilku testów wydajnościowych na przykładowych danych, przede wszystkim zredukowaliśmy je do jak najmniejszej liczby instrukcji, a także jak najmniejszej liczby kopii pamięci włączonej za pomocą klasy podciągu, która odwołuje się tylko do przesunięć / długości w innych łańcuchach. (Przetasowałem własne, ale są też inne implementacje). Niestety, nie można zrobić zbyt wiele, aby to poprawić, ale możliwe były przyrosty przyrostowe. - Marius
To jest właściwy wynik dla kiedy trimEmpty = true. Weź pod uwagę, że "abo" nie jest ogranicznikiem w tej odpowiedzi, ale listą znaków ogranicznika. Byłoby łatwo zmodyfikować go tak, aby wziął pojedynczy łańcuch znaków (myślę str.find_first_ofpowinien się zmienić na str.find_first, ale mogę się mylić ... nie mogę przetestować) - Marius


Oto inne rozwiązanie. Jest kompaktowy i racjonalnie 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żna go łatwo dopasować do obsługi separatorów łańcuchów, szerokich sznurków itp.

Zwróć uwagę na podział "" skutkuje pojedynczym pustym łańcuchem i podziałem "," (sep) daje dwa puste ciągi.

Można go również łatwo rozwinąć, aby pominąć 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żądane jest dzielenie ciągu znaków na wiele ograniczników przy jednoczesnym pomijaniu 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;
}

150



Pierwsza wersja jest prosta i doskonale wykonuje swoją pracę. Jedyną zmianą, jaką chciałbym wprowadzić, byłoby zwrócenie wyniku bezpośrednio, zamiast podania go jako parametru. - gregschlom
Dane wyjściowe są przekazywane jako parametr wydajności. Jeśli wynik zostałby zwrócony, wymagałby albo kopii wektora, albo alokacji sterty, która musiałaby wtedy zostać zwolniona. - Alec Thomas
Niewielki dodatek do powyższego komentarza: funkcja ta może zwrócić wektor bez kar, jeśli używa się semantyki ruchu C ++ 11. - Alec Thomas
@AlecThomas: Czy nawet przed C ++ 11 większość kompilatorów nie zoptymalizuje kopii zwrotnej przez NRVO? (+1 w każdym razie, bardzo zwięźle) - Marcelo Cantos
Spośród wszystkich odpowiedzi wydaje się, że jest to jeden z najbardziej atrakcyjnych i elastycznych. Wraz z getline z ogranicznikiem, choć jest to mniej oczywiste rozwiązanie. Czy standard c ++ 11 nie ma w tym nic? Czy c ++ 11 obsługuje obecnie karty dziurkacza? - Spacen Jasset


To jest mój ulubiony sposób na iterowanie ciągu znaków. Możesz robić, co chcesz, dla każdego słowa.

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

istringstream iss(line, istringstream::in);

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

106



Czy możliwe jest zadeklarowanie word jak char? - abatishchev
Przepraszam abatishchev, C ++ nie jest moją mocną stroną. Ale wyobrażam sobie, że nie byłoby trudno dodać wewnętrzną pętlę do przechodzenia przez każdy znak w każdym słowie. Ale teraz uważam, że pętla prądowa zależy od przestrzeni dla separacji słów. O ile nie wiesz, że pomiędzy każdą przestrzenią jest tylko jedna postać, w takim przypadku możesz po prostu rzucić "słowo" w char ... Przykro mi, nie mogę być bardziej pomocna, ive znaczyło odświeżyć moje C ++ - gnomed
jeśli zadeklarujesz słowo jako znak, będzie ono iterować nad każdą nie-białą spacją. Wystarczy spróbować: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner


Jest to podobne do pytania w Stack Overflow Jak mogę tokenizować ciąg znaków 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;
    }
}

76



Czy to zmaterializuje kopię wszystkich tokenów, czy też zachowuje tylko początkową i końcową pozycję bieżącego tokena? - einpoklum


Podoba mi się to, ponieważ umieszcza wyniki w wektorze, obsługuje łańcuch jako delim i daje kontrolę nad zachowaniem pustych wartości. Ale nie wygląda wtedy 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 split() to działa częściowo w ten sposób. A jeśli przez "białą przestrzeń", naprawdę masz na myśli dowolny typ białej przestrzeni, używając podziału Boost'a is_any_of() działa świetnie.


65



Wreszcie rozwiązanie, które poprawnie obsługuje puste żetony po obu stronach sznurka - fmuecke


STL nie ma już takiej dostępnej metody.

Możesz jednak użyć C strtok() funkcja przy użyciu std::string::c_str() członka lub możesz napisać własną. Oto przykład kodu znaleziony po szybkim wyszukiwaniu 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);
    }
}

Pochodzi 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 wytłumaczę.

I tylko dlatego, że nie implementuje a typedef zwany iteratorem lub przeciążenie << operator nie oznacza, że ​​jest to zły kod. Często używam funkcji C. Na przykład, printf i scanf oba są szybsze niż std::cin i std::cout (znacznie), fopen Składnia jest o wiele bardziej przyjazna dla typów binarnych i ma tendencję do tworzenia mniejszych plików EXE.

Nie daj się sprzedać w tej sprawie "Elegancja nad wydajnością" sprawa.


48



Jestem świadomy funkcji ciągów C i jestem również świadomy problemów z wydajnością (oba z nich zauważyłem w moim pytaniu). Jednak w przypadku tego konkretnego pytania szukam eleganckiego rozwiązania C ++. - Ashwin Nanjappa
@Nelson LaQuet: Niech zgadnę: ponieważ strtok nie jest reentrantem? - paercebal
@Nelson nie zawsze pass string.c_str () na strtok! strtok przechowuje łańcuch wejściowy (wstawia znaki \ 0 'w celu zastąpienia każdego separatora foudn), a c_str () zwraca łańcuch niemodyfikowalny. - Evan Teran
@Nelson: Ta tablica musi mieć rozmiar str.size () + 1 w twoim ostatnim komentarzu. Ale zgadzam się z tezą, że głupio jest unikać funkcji C z powodów "estetycznych". - j_random_hacker
@paulm: Nie, powolność strumieni C ++ jest spowodowana aspektami. Wciąż są wolniejsze od funkcji stdio.h, nawet jeśli synchronizacja jest wyłączona (i w strumieniach, których nie można zsynchronizować). - Ben Voigt