Frage Die eleganteste Art, die Wörter einer Zeichenfolge zu durchlaufen [geschlossen]


Was ist der eleganteste Weg, um die Wörter einer Zeichenkette zu wiederholen? Es kann angenommen werden, dass die Zeichenfolge aus durch Leerzeichen getrennten Wörtern besteht.

Beachte, dass ich mich nicht für C-String-Funktionen oder diese Art von Zeichenmanipulation / -zugriff interessiere. Auch, bitte geben Sie Eleganz Vorrang vor Effizienz in Ihrer Antwort.

Die beste Lösung, die ich gerade 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);
}

2638


Ursprung


Antworten:


Für das, was es wert ist, ist hier eine andere Möglichkeit, Token aus einer Eingabezeichenfolge zu extrahieren, die sich nur auf Standardbibliotheksfunktionen verlässt. Es ist ein Beispiel für die Kraft und Eleganz hinter dem Design der 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 Ausgabestream zu kopieren, könnten Sie sie in einen Container einfügen, indem Sie denselben generischen Code verwenden copy Algorithmus.

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

... oder erstelle das vector direkt:

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

1188



Ich verwende das, um die Zeichenfolge durch ein Trennzeichen aufzuteilen. Die erste setzt die Ergebnisse in einen vorkonstruierten 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::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;
}

Beachten Sie, dass bei dieser Lösung keine leeren Token übersprungen werden. Daher finden Sie im Folgenden vier Elemente, von denen eines leer ist:

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

2307



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 der stringstream Ansatz. Und da dies eine generische Template-Funktion ist, kann sie verwendet werden, um andere Stringtypen (wchar usw. oder UTF-8) unter Verwendung aller Arten von Trennzeichen zu teilen.

Siehe die Dokumentation für Details.


794



#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



Für diejenigen, mit denen es nicht gut ist, alle Effizienz für die Codegröße zu opfern und "effizient" als eine Art von Eleganz zu sehen, sollte das Folgende einen guten Platz finden (und ich denke, dass die Vorlagencontainerklasse eine erstaunlich elegante Ergänzung ist):

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 wähle normalerweise zu verwenden std::vector<std::string> Typen als mein zweiter Parameter (ContainerT)... aber list<> ist viel schneller als vector<> wenn Direktzugriff nicht benötigt wird und Du sogar Deine eigene String-Klasse erstellen kannst und sowas verwenden kannst std::list<subString> woher subString macht keine Kopien für unglaubliche Geschwindigkeitssteigerungen.

Es ist mehr als doppelt so schnell wie das schnellste Tokenize auf dieser Seite und fast 5 mal schneller als andere. Auch mit den perfekten Parametertypen können Sie alle String- 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 Token als Referenz, so dass Sie Tokens auch mit mehreren Aufrufen erstellen können, wenn Sie dies wünschen.

Schließlich können Sie festlegen, ob leere Token aus den Ergebnissen über einen letzten optionalen Parameter abgeschnitten 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.


168



Hier ist eine andere Lösung. Es ist kompakt und einigermaßen 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 templatisiert werden, um String-Separatoren, breite Strings usw. zu handhaben.

Beachten Sie das Aufteilen "" ergibt eine einzelne leere Zeichenfolge und Splitting "," (dh. sep) führt zu zwei leeren Strings.

Es kann auch leicht 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 eine Zeichenkette an mehreren Trennzeichen aufgeteilt werden soll, während leere Token übersprungen werden, 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;
}

150



Dies ist meine bevorzugte Methode, um eine Zeichenfolge zu durchlaufen. Sie können tun, was Sie wollen, pro Wort.

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