3379 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?

693 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?

45voto

Marco M. Punkte 2709

Hier ist eine Splitfunktion, die:

  • ist generisch
  • verwendet Standard-C++ (kein Boost)
  • akzeptiert mehrere Begrenzungszeichen
  • ignoriert leere Token (kann leicht geändert werden)

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

Beispiel für die Verwendung:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

0 Stimmen

Sie haben vergessen, die Verwendungsliste zu ergänzen: "extrem ineffizient"

1 Stimmen

@XanderTulip, können Sie etwas konstruktiver sein und erklären, wie oder warum?

3 Stimmen

@XanderTulip: Ich nehme an, Sie meinen, dass es den Vektor als Wert zurückgibt. Die Return-Value-Optimierung (RVO, googeln Sie es) sollte sich darum kümmern. In C++11 kann man auch per Move-Referenz zurückgeben.

39voto

rhomu Punkte 176

Ich habe eine 2-Zeilen-Lösung für dieses Problem:

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;

Anstatt sie zu drucken, können Sie sie dann in einen Vektor einfügen.

2 Stimmen

Es ist nur ein Zweizeiler, weil eine dieser beiden Zeilen riesig und kryptisch ist... niemand, der tatsächlich jemals Code lesen muss, will so etwas lesen oder würde es schreiben. gekünstelte Kürze ist schlimmer als geschmackvolle Weitschweifigkeit.

37voto

Robert Punkte 2200

Ein weiterer flexibler und schneller Weg

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

Um es mit einem Vektor von Zeichenfolgen zu verwenden (Bearbeiten: Da jemand darauf hingewiesen, nicht zu erben STL-Klassen... 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");

Das war's! Und das ist nur eine Möglichkeit, den Tokenizer zu verwenden, z.B. wie man einfach Wörter zählen:

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

Begrenzt durch die Vorstellungskraft ;)

0 Stimmen

37voto

dk123 Punkte 16794

Hier ist eine einfache Lösung, die nur die Standard-Regex-Bibliothek verwendet

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

Das Argument regex ermöglicht die Überprüfung auf mehrere Argumente (Leerzeichen, Kommas usw.)

Normalerweise prüfe ich nur, ob ich an Leerzeichen und Kommas trennen kann, daher habe ich auch diese Standardfunktion:

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

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

    return Tokenize( str, re );
}

El "[\\s,]+" prüft auf Leerzeichen ( \\s ) und Kommas ( , ).

Hinweis: Wenn Sie die wstring anstelle von string ,

  • alle ändern std::regex a std::wregex
  • alle ändern sregex_token_iterator a wsregex_token_iterator

Beachten Sie, dass Sie das String-Argument je nach Compiler auch als Referenz verwenden können.

0 Stimmen

Das wäre meine Lieblingsantwort gewesen, aber std::regex ist in GCC 4.8 kaputt. Sie sagten, dass sie es in GCC 4.9 korrekt implementiert haben. Ich gebe Ihnen trotzdem mein +1

1 Stimmen

Dies ist mein Favorit mit geringfügigen Änderungen: Vektor als Referenz zurückgegeben, wie Sie sagte, und die Argumente "str" und "regex" durch Referenzen auch übergeben. thx.

1 Stimmen

Rohzeichenketten sind beim Umgang mit Regex-Mustern recht nützlich. Auf diese Weise müssen Sie die Escape-Sequenzen nicht verwenden... Sie können einfach Folgendes verwenden R"([\s,]+)" .

33voto

KTC Punkte 8899

Verwendung von std::stringstream wie Sie es haben, funktioniert einwandfrei und erfüllt genau das, was Sie wollten. Wenn Sie einfach nur nach einer anderen Vorgehensweise suchen, können Sie Folgendes verwenden std::find() / std::find_first_of() y std::string::substr() .

Hier ist ein Beispiel:

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

1 Stimmen

Dies funktioniert nur bei einstelligen Begrenzungszeichen. Mit einer einfachen Änderung funktioniert es auch bei mehrstelligen Zeichen: prev_pos = pos += delimiter.length();

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