Wie iteriere ich über die Wörter einer Zeichenfolge?

Lesezeit: 9 Minuten

Ich versuche, über die Wörter einer Zeichenfolge zu iterieren.

Es kann davon ausgegangen werden, dass die Zeichenfolge aus Wörtern besteht, die durch Leerzeichen getrennt sind.

Beachten Sie, dass ich nicht an C-String-Funktionen oder dieser Art von Zeichenmanipulation/Zugriff interessiert bin. Bitte geben Sie in Ihrer Antwort auch der Eleganz den Vorrang vor der Effizienz.

Die beste Lösung, die ich jetzt habe, ist:

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

Gibt es eine elegantere Möglichkeit, dies zu tun?

  • Alter … Eleganz ist in meinem Buch nur eine schicke Art, “Effizienz-die-hübsch-aussieht” zu sagen. Scheuen Sie sich nicht, C-Funktionen und schnelle Methoden zu verwenden, um etwas zu erreichen, nur weil es nicht in einer Vorlage enthalten ist;)

    Benutzer19302

    25. Oktober 2008 um 9:04 Uhr

  • while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }

    – pyon

    29. September 2009 um 15:47 Uhr

  • @Eduardo: Das ist auch falsch … Sie müssen iss zwischen dem Versuch, einen anderen Wert zu streamen, und der Verwendung dieses Werts testen, dh string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';

    – Toni Delroy

    11. April 2012 um 2:24 Uhr

  • Verschiedene Optionen in C++, um dies standardmäßig zu tun: cplusplus.com/faq/sequences/strings/split

    – hB0

    31. Oktober 2013 um 0:23 Uhr

  • Eleganz ist mehr als nur hübsche Effizienz. Zu den eleganten Attributen gehören eine geringe Zeilenzahl und eine gute Lesbarkeit. IMHO Eleganz ist kein Proxy für Effizienz, sondern Wartbarkeit.

    – Matt

    31. März 2017 um 13:22 Uhr

Für das, was es wert ist, hier ist eine weitere Möglichkeit, Tokens aus einer Eingabezeichenfolge zu extrahieren, wobei nur auf Standardbibliotheksfunktionen zurückgegriffen wird. Es ist ein Beispiel für die Kraft und Eleganz hinter dem Design des 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"));
}

Anstatt die extrahierten Token in einen Ausgabestrom zu kopieren, könnte man sie unter Verwendung desselben Generikums in einen Container einfügen copy Algorithmus.

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

… oder erstellen Sie die vector direkt:

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

  • Kann man dafür ein Trennzeichen angeben? Wie zum Beispiel das Aufteilen auf Kommas?

    – l3dx

    6. August 2009 um 11:49 Uhr

  • @Jonathan: \n ist in diesem Fall nicht das Trennzeichen, sondern das Trennzeichen für die Ausgabe an cout.

    – hey

    3. Februar 2010 um 12:37 Uhr

  • Dies ist eine schlechte Lösung, da sie kein anderes Trennzeichen akzeptiert und daher nicht skalierbar und nicht wartbar ist.

    – Kleines Schach

    10. Januar 2011 um 3:57 Uhr

  • Eigentlich das kann funktionieren gut mit anderen Trennzeichen (obwohl es etwas hässlich ist, einige zu machen). Sie erstellen eine ctype-Facette, die die gewünschten Trennzeichen als Leerzeichen klassifiziert, erstellen ein Gebietsschema, das dieses Facet enthält, und versehen dann den Stringstream mit diesem Gebietsschema, bevor Sie Zeichenfolgen extrahieren.

    – Jerry Sarg

    19. Dezember 2012 um 20:30 Uhr

  • @Kinderschokolade “Es kann davon ausgegangen werden, dass der String aus Wörtern besteht, die durch Leerzeichen getrennt sind.” – Hmm, klingt nicht nach einer schlechten Lösung für das Problem der Frage. “nicht skalierbar und nicht wartbar” – Hah, nett.

    – Christian Rau

    7. Februar 2013 um 15:08 Uhr

  • Geschwindigkeit ist hier irrelevant, da beide Fälle viel langsamer sind als eine strtok-ähnliche Funktion.

    – Tom

    1. März 2009 um 16:51 Uhr

  • Und für diejenigen, die boost noch nicht haben… bcp kopiert dafür über 1.000 Dateien 🙂

    – Roman Starkow

    9. Juni 2010 um 20:12 Uhr

  • Achtung, wenn eine leere Zeichenkette (“”) übergeben wird, gibt diese Methode einen Vektor zurück, der die Zeichenkette “” enthält. Fügen Sie also vor der Teilung ein “if (!string_to_split.empty())” hinzu.

    – Firmmo

    11. Oktober 2011 um 13:10 Uhr

  • @Ian Embedded-Entwickler verwenden nicht alle Boost.

    – ACK_stoverflow

    31. Januar 2012 um 18:23 Uhr

  • Als Nachtrag: Ich verwende Boost nur, wenn ich muss, normalerweise ziehe ich es vor, meiner eigenen Bibliothek Code hinzuzufügen, der eigenständig und portabel ist, damit ich kleinen, präzisen, spezifischen Code erreichen kann, der ein bestimmtes Ziel erreicht. Auf diese Weise ist der Code nicht öffentlich, performant, trivial und portabel. Boost hat seinen Platz, aber ich würde vorschlagen, dass es ein bisschen übertrieben ist, um Saiten zu symbolisieren: Sie würden nicht Ihr ganzes Haus zu einem Ingenieurbüro transportieren lassen, um einen neuen Nagel in die Wand hämmern zu lassen, um ein Bild aufzuhängen … sie können es tun sehr gut, aber die Vorteile überwogen bei weitem durch die Nachteile.

    – G Masucci

    22. Mai 2013 um 8:19 Uhr


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

  • Sie können auch andere Trennzeichen verwenden, wenn Sie diese verwenden getline in dem while Bedingung zB durch Kommas trennen, verwenden while(getline(ss, buff, ',')).

    – Ali

    6. Oktober 2018 um 20:20 Uhr

Für diejenigen, denen es nicht passt, alle Effizienz für die Codegröße zu opfern und “effizient” als eine Art Eleganz zu sehen, sollte das Folgende einen Sweet Spot treffen (und ich denke, die Template-Container-Klasse ist eine unglaublich elegante Ergänzung.):

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

Ich entscheide mich normalerweise für die Verwendung std::vector<std::string> Typen als mein zweiter Parameter (ContainerT)… aber list<> ist viel schneller als vector<> für den Fall, dass kein direkter Zugriff erforderlich ist, und Sie können sogar Ihre eigene Zeichenfolgenklasse erstellen und so etwas wie verwenden std::list<subString> wo subString macht keine Kopien für unglaubliche Geschwindigkeitssteigerungen.

Es ist mehr als doppelt so schnell wie das schnellste Tokenize auf dieser Seite und fast fünfmal schneller als einige andere. Auch mit den perfekten Parametertypen können Sie alle Zeichenfolgen- und Listenkopien für zusätzliche Geschwindigkeitssteigerungen eliminieren.

Außerdem führt es nicht die (äußerst ineffiziente) Rückgabe des Ergebnisses durch, sondern übergibt die Tokens als Referenz, sodass Sie auch Tokens mit mehreren Aufrufen erstellen können, wenn Sie dies wünschen.

Schließlich können Sie über einen letzten optionalen Parameter angeben, ob leere Token aus den Ergebnissen entfernt werden sollen.

Alles was es braucht ist std::string… der Rest ist optional. Es verwendet keine Streams oder die Boost-Bibliothek, ist aber flexibel genug, um einige dieser fremden Typen natürlich akzeptieren zu können.

  • Sie können auch andere Trennzeichen verwenden, wenn Sie diese verwenden getline in dem while Bedingung zB durch Kommas trennen, verwenden while(getline(ss, buff, ',')).

    – Ali

    6. Oktober 2018 um 20:20 Uhr

Hier ist eine andere Lösung. Es ist kompakt und ziemlich effizient:

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

Es kann leicht mit Vorlagen versehen werden, um Zeichenfolgentrenner, breite Zeichenfolgen usw. zu handhaben.

Beachten Sie diese Aufteilung "" führt zu einer einzelnen leeren Zeichenfolge und Aufspaltung "," (zB sep) ergibt zwei leere Strings.

Es kann auch einfach erweitert werden, um leere Token zu überspringen:

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

Wenn das Aufteilen einer Zeichenfolge an mehreren Trennzeichen unter Überspringen leerer Token gewünscht wird, kann diese Version verwendet werden:

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

  • Die erste Version ist einfach und erledigt die Arbeit perfekt. Die einzige Änderung, die ich vornehmen würde, wäre, das Ergebnis direkt zurückzugeben, anstatt es als Parameter zu übergeben.

    – gregschlom

    19. Januar 2012 um 2:25 Uhr

  • Die Leistung wird als Parameter für die Effizienz übergeben. Wenn das Ergebnis zurückgegeben würde, wäre entweder eine Kopie des Vektors oder eine Heap-Zuweisung erforderlich, die dann freigegeben werden müsste.

    – Alex Thomas

    6. Februar 2012 um 18:56 Uhr

  • @AlecThomas: Würden die meisten Compiler nicht schon vor C ++ 11 die Rückkopie über NRVO optimieren? (+1 sowieso; sehr prägnant)

    – Marcelo Cantos

    17. August 2013 um 11:54 Uhr


  • Von allen Antworten scheint dies eine der attraktivsten und flexibelsten zu sein. Zusammen mit getline mit einem Trennzeichen, obwohl es eine weniger offensichtliche Lösung ist. Hat der c++11-Standard nichts dafür? Unterstützt c++11 heutzutage Lochkarten?

    – Spacen Jasset

    11. August 2015 um 15:15 Uhr


  • Schlagen Sie vor, std::string::size_type anstelle von int zu verwenden, da einige Compiler ansonsten signierte/unsignierte Warnungen ausspucken könnten.

    – Pascal Kesseli

    1. November 2015 um 20:45 Uhr

1003640cookie-checkWie iteriere ich über die Wörter einer Zeichenfolge?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy