3325 Stimmen

Wie kann ich über die Wörter einer Zeichenkette iterieren?

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

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

Beachten Sie, dass ich nicht in C-String-Funktionen oder diese 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 im Moment 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?

686 Stimmen

Kumpel... Eleganz ist in meinen Augen nur eine schicke Umschreibung für "Effizienz, die hübsch aussieht". Scheuen Sie sich nicht, C-Funktionen und schnelle Methoden zu verwenden, um etwas zu erreichen, nur weil es nicht in einer Vorlage enthalten ist ;)

19 Stimmen

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

0 Stimmen

@nlaq, Außer, dass Sie Ihr String-Objekt mit c_str() konvertieren müssten, und wieder zurück in einen String, wenn Sie es immer noch benötigen, um eine Zeichenfolge zu sein, nicht?

2569voto

Evan Teran Punkte 83711

Ich verwende dies, um eine Zeichenkette durch ein Begrenzungszeichen zu trennen. Die erste setzt die Ergebnisse in einen vordefinierten Vektor, die zweite gibt einen neuen Vektor zurück.

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

Beachten Sie, dass diese Lösung keine leeren Token überspringt, so dass im Folgenden 4 Einträge gefunden werden, von denen einer leer ist:

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

0 Stimmen

Elegante Lösung, ich vergesse immer diese spezielle "getline", denn ich glaube nicht, dass sie Anführungszeichen und Escape-Sequenzen kennt.

0 Stimmen

@stijn: Wollen Sie damit sagen, dass split("one two three", ' '); gibt einen Vektor mit 4 Elementen zurück? Ich bin nicht sicher, ob das der Fall ist, aber ich werde es testen.

0 Stimmen

Moment, es scheint, dass die Formatierung einige Leerzeichen entfernt hat (oder ich habe sie vergessen): Ich spreche von der Zeichenfolge "eins zwei drei" mit 2 Leerzeichen zwischen "zwei" und "drei".

1502voto

Zunino Punkte 2034

Hier ist eine weitere Möglichkeit, Token aus einer Eingabezeichenkette zu extrahieren, die nur auf Standardbibliotheksfunktionen zurückgreift. Dies ist ein Beispiel für die Leistungsfähigkeit und Eleganz, die hinter dem Design der STL steckt.

#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 in einen Container einfügen, indem man die gleiche generische 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>{}};

177 Stimmen

Ist es möglich, dafür ein Trennzeichen anzugeben? Wie zum Beispiel die Trennung durch Kommas?

7 Stimmen

@l3dx: es scheint, dass der Parameter " \n " ist das Begrenzungszeichen. Dieser Code ist sehr schön, aber ich würde gerne mehr darüber wissen. Vielleicht könnte jemand jede Zeile dieses Schnipsels erklären?

17 Stimmen

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

871voto

ididak Punkte 5654

Eine mögliche Lösung mit Boost könnte sein:

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

Dieser Ansatz könnte sogar schneller sein als die stringstream Ansatz. Da es sich um eine generische Vorlagenfunktion handelt, können auch andere Zeichenkettentypen (wchar usw. oder UTF-8) mit allen möglichen Begrenzungszeichen aufgeteilt werden.

Siehe die Dokumentation für Details.

36 Stimmen

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

4 Stimmen

Das ist praktisch und schnell genug, wenn Sie wissen, dass die Zeile nur ein paar Token enthält, aber wenn sie viele enthält, verbrauchen Sie eine Menge Speicher (und Zeit), um den Vektor zu vergrößern. Also nein, es ist nicht schneller als die Stringstream-Lösung - zumindest nicht für große n, was der einzige Fall ist, in dem Geschwindigkeit eine Rolle spielt.

54 Stimmen

Und für diejenigen, die noch keinen Boost haben... bcp kopiert dafür über 1.000 Dateien :)

412voto

kev Punkte 145226
#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;
}

25 Stimmen

Sie können auch an anderen Begrenzungszeichen aufteilen, wenn Sie getline dans le while Bedingung, z. B. zum Trennen durch Kommas, verwenden Sie while(getline(ss, buff, ',')) .

0 Stimmen

Ich verstehe nicht, wie das 400 Bewertungen bekommen hat. Das ist im Grunde das Gleiche wie in OQ: einen Stringstream verwenden und >> daraus. Genau das, was OP auch in Revision 1 der Fragegeschichte gemacht hat.

199voto

Marius Punkte 3250

Für diejenigen, die es nicht gut finden, alle Effizienz für die Codegröße zu opfern und "effizient" als eine Art von Eleganz zu sehen, sollte das Folgende einen guten Punkt 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 verwende normalerweise std::vector<std::string> Typen als zweiten Parameter ( ContainerT )... aber list<> ist viel schneller als vector<> für den Fall, dass ein direkter Zugriff nicht erforderlich ist, und Sie können sogar Ihre eigene String-Klasse erstellen und etwas verwenden wie std::list<subString> wobei 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 so schnell wie einige andere. Mit den perfekten Parametertypen können Sie auch alle String- und Listenkopien eliminieren, was die Geschwindigkeit zusätzlich erhöht.

Außerdem erfolgt keine (äußerst ineffiziente) Rückgabe des Ergebnisses, sondern die Token werden als Verweis übergeben, so dass Sie auch Token über mehrere Aufrufe aufbauen 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 zu akzeptieren.

5 Stimmen

Ich bin ein ziemlicher Fan davon, aber für G++ (und wahrscheinlich gute Praxis) jeder, der dies verwendet, wird typedefs und typenames wollen: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Dann ersetzen Sie die value_type und size_types entsprechend.

13 Stimmen

Für diejenigen von uns, denen die Vorlagen und der erste Kommentar völlig fremd sind, wäre ein Anwendungsbeispiel mit den erforderlichen Includes schön.

3 Stimmen

Ah, ich habe es herausgefunden. Ich habe die C++-Zeilen aus aws' Kommentar in den Funktionskörper von tokenize() eingefügt und dann die tokens.push_back()-Zeilen bearbeitet, um ContainerT::value_type in ValueType und (ContainerT::value_type::size_type) in (SizeType) zu ändern. Die Bits, über die g++ gejammert hat, wurden behoben. Rufen Sie es einfach als tokenize( some_string, some_vector ) auf;

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X