Pregunta La forma más elegante de repetir las palabras de una cadena [cerrado]


¿Cuál es la forma más elegante de iterar las palabras de una cadena? Se puede suponer que la cadena está compuesta de palabras separadas por espacios en blanco.

Tenga en cuenta que no estoy interesado en las funciones de cadena C o ese tipo de manipulación / acceso de caracteres. Además, dé prioridad a la elegancia sobre la eficiencia en su respuesta.

La mejor solución que tengo ahora es:

#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


origen


Respuestas:


Por lo que vale, esta es otra forma de extraer tokens de una cadena de entrada, confiando solo en las instalaciones de biblioteca estándar. Es un ejemplo del poder y la elegancia detrás del diseño del 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"));
}

En lugar de copiar los tokens extraídos a una secuencia de salida, se pueden insertar en un contenedor, utilizando el mismo genérico copy algoritmo.

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

... o crea el vector directamente:

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

1188



Yo uso esto para dividir la cadena por un delimitador. El primero pone los resultados en un vector preconstruido, el segundo devuelve un nuevo vector.

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

Tenga en cuenta que esta solución no omite tokens vacíos, por lo que lo siguiente encontrará 4 elementos, uno de los cuales está vacío:

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

2307



Una posible solución usando Boost podría ser:

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

Este enfoque podría ser incluso más rápido que el stringstream enfoque. Y dado que se trata de una función de plantilla genérica, puede utilizarse para dividir otros tipos de cadenas (wchar, etc. o UTF-8) utilizando todo tipo de delimitadores.

Ver el documentación para detalles.


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



Para aquellos con los que no se siente bien sacrificar toda la eficiencia por el tamaño del código y ver "eficiente" como un tipo de elegancia, lo siguiente debería alcanzar un punto óptimo (y creo que la clase de contenedor de plantilla es una adición asombrosamente 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;
   }
}

Por lo general, elijo usar std::vector<std::string> tipos como mi segundo parámetro (ContainerT)... pero list<> es mucho más rápido que vector<> para cuando el acceso directo no es necesario, e incluso puede crear su propia clase de cuerda y usar algo como std::list<subString> dónde subString no hace ninguna copia para aumentos de velocidad increíbles.

Es más del doble de rápido que el token más rápido en esta página y casi 5 veces más rápido que otros. Además, con los tipos de parámetros perfectos, puede eliminar todas las cadenas y copias de lista para aumentos de velocidad adicionales.

Además, no hace el retorno de resultados (extremadamente ineficiente), sino que pasa los tokens como referencia, lo que también te permite crear tokens usando múltiples llamadas si así lo deseas.

Por último, le permite especificar si desea recortar tokens vacíos de los resultados a través de un último parámetro opcional.

Todo lo que necesita es std::string... el resto es opcional. No usa streams o la biblioteca de boost, pero es lo suficientemente flexible como para poder aceptar algunos de estos tipos foráneos de forma natural.


168



Aquí hay otra solución. Es compacto y razonablemente eficiente:

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

Puede ser templado fácilmente para manejar separadores de cuerdas, cuerdas anchas, etc.

Tenga en cuenta que la división "" da como resultado una sola cadena vacía y división "," (es decir, sep) da como resultado dos cadenas vacías.

También se puede expandir fácilmente para omitir tokens vacíos:

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

Si desea dividir una cadena en múltiples delimitadores mientras omite tokens vacíos, esta versión puede ser utilizada:

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



Esta es mi forma favorita de iterar a través de una cadena. Puedes hacer lo que quieras por palabra.

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



Esto es similar a la pregunta de desbordamiento de pila ¿Cómo puedo tokenizar una cadena en 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



Me gusta lo siguiente porque pone los resultados en un vector, admite una cadena como un delim y le da control sobre mantener los valores vacíos. Pero, no se ve tan bien entonces.

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

Por supuesto, Boost tiene una split() eso funciona parcialmente así. Y, si por 'espacio en blanco', realmente quiere decir cualquier tipo de espacio en blanco, usando la división de Boost con is_any_of() Funciona genial.


65



El STL ya no tiene dicho método disponible.

Sin embargo, puedes usar C's strtok() función mediante el uso de la std::string::c_str() miembro, o puede escribir el suyo. Aquí hay una muestra de código que encontré después de una búsqueda rápida en Google ("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);
    }
}

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

Si tiene preguntas sobre la muestra del código, deje un comentario y lo explicaré.

Y solo porque no implementa un typedef llamado iterador o sobrecargar el << operador no quiere decir que es un código incorrecto. Uso las funciones C con bastante frecuencia. Por ejemplo, printf y scanf ambos son más rápidos que std::cin y std::cout (significativamente), el fopen la sintaxis es mucho más amigable para los tipos binarios, y también tienden a producir EXE más pequeños.

No te vengas vendido en esto "Elegancia sobre el rendimiento" acuerdo.


48