[c++] Die eleganteste Art, die Wörter einer Zeichenfolge zu durchlaufen


Answers

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. Im Folgenden finden Sie daher 4 Elemente, von denen eines leer ist:

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

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



Bisher habe ich das in Boost , aber ich brauchte etwas, was nicht davon abhängt, also kam ich dazu:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Ein guter Punkt ist, dass Sie in separators mehr als ein Zeichen übergeben können.




What about this:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}



Heres ist eine Regex-Lösung, die nur die Standard-Regex-Bibliothek verwendet. (Ich bin etwas eingerostet, daher kann es ein paar Syntaxfehler geben, aber das ist zumindest die allgemeine Idee)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}



Short and elegant

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

can use any string as delimiter, also can be used with binary data (std::string supports binary data, including nulls)

using:

auto a = split("this!!is!!!example!string", "!!");

Ausgabe:

this
is
!example!string



I like to use the boost/regex methods for this task since they provide maximum flexibility for specifying the splitting criteria.

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}



Dies ist vergleichbar mit der Stapelüberlauf-Frage Wie kann ich eine Zeichenfolge in C ++ in Tokens konvertieren? .

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



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 in einen Vektor einfügen.




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, dass die Aufspaltung von "" zu einer einzelnen leeren Zeichenkette führt und die Aufspaltung "," (z. B. sep) zu zwei leeren Zeichenketten führt.

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



#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}



Get Boost ! : -)

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace boost;

int main(int argc, char**argv) {
    typedef vector < string > list_type;

    list_type list;
    string line;

    line = "Somewhere down the road";
    split(list, line, is_any_of(" "));

    for(int i = 0; i < list.size(); i++)
    {
        cout << list[i] << endl;
    }

    return 0;
}

This example gives the output -

Somewhere
down
the
road



#include <vector>
#include <string>
#include <sstream>

using namespace std;

int main()
{
    string str("Split me by whitespaces");
    string buf; // Have a buffer string
    stringstream ss(str); // Insert the string into a stream

    vector<string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);
}



Eine solche Methode steht der STL noch nicht zur Verfügung.

Sie können jedoch entweder die strtok() Funktion von C verwenden, indem Sie das std::string::c_str() , oder Sie können Ihre eigenen schreiben. Hier ist ein Codebeispiel, das ich nach einer schnellen Google-Suche gefunden habe ( "STL string split" ):

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

Genommen von: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Wenn Sie Fragen zum Codebeispiel haben, hinterlassen Sie einen Kommentar und ich erkläre es Ihnen.

Und nur weil es kein typedef namens typedef oder overload implementiert, typedef der Operator << nicht, dass es schlechter Code ist. Ich benutze C-Funktionen ziemlich häufig. Zum Beispiel sind printf und scanf beide schneller als std::cin und std::cout (signifikant), die fopen Syntax ist viel freundlicher für binäre Typen und sie tendieren auch dazu, kleinere EXEs zu erzeugen.

Nicht auf diesen "Eleganz über Leistung" Deal verkauft werden.




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 Regex-Argument ermöglicht die Überprüfung auf mehrere Argumente (Leerzeichen, Kommas usw.)

Ich überprüfe normalerweise nur, um Leerzeichen und Kommas zu trennen, also habe ich auch diese Standardfunktion:

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

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

    return Tokenize( str, re );
}

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

Beachten Sie, wenn Sie wstring anstelle von string teilen wstring ,

  • ändere alle std::regex in std::wregex
  • ändere alle sregex_token_iterator in wsregex_token_iterator

Beachten Sie, dass Sie das string-Argument je nach Compiler auch als Referenz verwenden möchten.




Verwenden Sie std::stringstream wie Sie haben, funktioniert einwandfrei, und tun genau das, was Sie wollten. Wenn Sie nur nach einer anderen Methode suchen, können Sie std::find() / std::find_first_of() und 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;
}



Related