Domanda Il modo più elegante per scorrere le parole di una stringa [chiuso]


Qual è il modo più elegante per ripetere le parole di una stringa? Si può presumere che la stringa sia composta da parole separate da spazi bianchi.

Nota che non mi interessano le funzioni di stringa C o quel tipo di manipolazione / accesso al personaggio. Inoltre, si prega di dare la precedenza all'eleganza rispetto all'efficienza nella propria risposta.

La soluzione migliore che ho adesso è:

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

2637


origine


risposte:


Per quello che vale, ecco un altro modo per estrarre i token da una stringa di input, basandosi solo su strutture di libreria standard. È un esempio della potenza e dell'eleganza che sta dietro al design della 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"));
}

Invece di copiare i token estratti in un flusso di output, è possibile inserirli in un contenitore, utilizzando lo stesso generico copy algoritmo.

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

... o creare il vector direttamente:

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

1188



Lo uso per dividere la stringa con un delimitatore. Il primo mette i risultati in un vettore pre-costruito, il secondo restituisce un nuovo vettore.

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

Nota che questa soluzione non salta i token vuoti, quindi il seguente troverà 4 elementi, uno dei quali è vuoto:

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

2307



Una possibile soluzione utilizzando Boost potrebbe essere:

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

Questo approccio potrebbe essere anche più veloce del stringstream approccio. E poiché questa è una funzione modello generica, può essere utilizzata per suddividere altri tipi di stringhe (wchar, ecc. O UTF-8) utilizzando tutti i tipi di delimitatori.

Vedere il documentazione per dettagli.


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



Per coloro con i quali non si trova bene a sacrificare tutta l'efficienza per le dimensioni del codice e vedere "efficiente" come un tipo di eleganza, il seguente dovrebbe colpire un punto debole (e penso che la classe contenitore template sia un'aggiunta incredibilmente elegante):

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

Di solito scelgo di usare std::vector<std::string> tipi come il mio secondo parametro (ContainerT)... ma list<> è molto più veloce di vector<> per quando l'accesso diretto non è necessario, e puoi anche creare la tua classe di stringhe e usare qualcosa del genere std::list<subString> dove subString non fa nessuna copia per incredibili aumenti di velocità.

È più del doppio più veloce del tokenize più veloce su questa pagina e quasi 5 volte più veloce di altri. Inoltre, con i tipi di parametri perfetti è possibile eliminare tutte le stringhe ed elencare le copie per aumenti di velocità aggiuntivi.

Inoltre, non restituisce il risultato (estremamente inefficiente) dei risultati, ma piuttosto trasmette i token come riferimento, consentendo in tal modo di creare token utilizzando più chiamate se lo si desidera.

Infine ti permette di specificare se tagliare i token vuoti dai risultati attraverso un ultimo parametro opzionale.

Tutto ciò di cui ha bisogno è std::string... il resto sono opzionali. Non usa gli stream o la libreria boost, ma è abbastanza flessibile da essere in grado di accettare alcuni di questi tipi stranieri in modo naturale.


168



Ecco un'altra soluzione. È compatto e abbastanza efficiente:

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

Può facilmente essere templato per gestire separatori di stringhe, stringhe larghe, ecc.

Si noti che la divisione "" produce una singola stringa vuota e la divisione "," (es: sep) restituisce due stringhe vuote.

Può anche essere facilmente espanso per saltare i token vuoti:

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

Se si desidera suddividere una stringa in più delimitatori mentre si ignorano i token vuoti, è possibile utilizzare questa versione:

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



Questo è il mio modo preferito per scorrere una stringa. Puoi fare tutto ciò che vuoi per parola.

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



Questo è simile alla domanda Stack Overflow Come faccio a tokenizzare una stringa in C ++?.

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

76



Mi piace quanto segue perché mette i risultati in un vettore, supporta una stringa come delim e dà il controllo sulla conservazione dei valori vuoti. Ma non sembra poi così bello.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Certo, Boost ha un split() funziona in parte in questo modo. E, se per "spazio bianco", intendi davvero qualsiasi tipo di spazio bianco, usando la divisione di Boost con is_any_of() funziona alla grande.


65



L'STL non ha già un tale metodo disponibile.

Tuttavia, puoi utilizzare C strtok() funzione usando il std::string::c_str() membro, o puoi scrivere il tuo. Ecco un esempio di codice che ho trovato dopo una rapida ricerca su Google ("STL split 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);
    }
}

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

Se hai domande sul codice di esempio, lascia un commento e ti spiegherò.

E solo perché non implementa a typedef chiamato iteratore o sovraccarico del << operatore non significa che è un codice cattivo. Uso le funzioni C abbastanza frequentemente. Per esempio, printf e scanf entrambi sono più veloci di std::cin e std::cout (significativamente), il fopen la sintassi è molto più amichevole per i tipi binari e tende anche a produrre EXE più piccoli.

Non essere venduto su questo "Eleganza rispetto alle prestazioni" affare.


48