¿Cómo itero sobre las palabras de una cadena?

9 minutos de lectura

Estoy tratando de iterar sobre 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 mismo 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);
}

¿Hay una manera más elegante de hacer esto?

  • Amigo… La elegancia es solo una forma elegante de decir “eficiencia que se ve bonita” en mi libro. No dude en usar funciones C y métodos rápidos para lograr cualquier cosa solo porque no está contenido en una plantilla;)

    usuario19302

    25 de octubre de 2008 a las 9:04

  • while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }

    – pión

    29 de septiembre de 2009 a las 15:47

  • @Eduardo: eso también está mal … debe probar iss entre intentar transmitir otro valor y usar ese valor, es decir string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';

    – Tony Delroy

    11 de abril de 2012 a las 2:24

  • Varias opciones en C++ para hacer esto por defecto: cplusplus.com/faq/secuencias/cadenas/split

    – hB0

    31 de octubre de 2013 a las 0:23

  • La elegancia es mucho más que una simple eficiencia. Los atributos elegantes incluyen un número bajo de líneas y una alta legibilidad. En mi humilde opinión, Elegance no es un indicador de eficiencia sino de mantenibilidad.

    – Mate

    31 de marzo de 2017 a las 13:22

Por lo que vale, aquí hay otra forma de extraer tokens de una cadena de entrada, confiando solo en las instalaciones de la 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 un flujo de salida, uno podría insertarlos en un contenedor, usando el mismo genérico copy algoritmo.

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

… o crear el vector directamente:

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

  • ¿Es posible especificar un delimitador para esto? ¿Como, por ejemplo, dividir entre comas?

    – l3dx

    6 de agosto de 2009 a las 11:49

  • @Jonathan: \n no es el delimitador en este caso, es el delimitador para generar cout.

    – huy

    3 de febrero de 2010 a las 12:37

  • Esta es una solución deficiente ya que no toma ningún otro delimitador, por lo tanto, no es escalable ni mantenible.

    – Ajedrez pequeño

    10 de enero de 2011 a las 3:57

  • En realidad, esto lata funciona bien con otros delimitadores (aunque hacer algunos es algo feo). Usted crea una faceta ctype que clasifica los delimitadores deseados como espacios en blanco, crea una configuración regional que contiene esa faceta, luego imbuye el flujo de cadenas con esa configuración regional antes de extraer las cadenas.

    – Jerry Ataúd

    19 dic 2012 a las 20:30

  • @Kinderchocolate “Se puede suponer que la cadena está compuesta de palabras separadas por espacios en blanco” – Hmm, no suena como una mala solución al problema de la pregunta. “no escalable y no mantenible” – Ja, agradable.

    – Christian Raú

    7 febrero 2013 a las 15:08

  • La velocidad es irrelevante aquí, ya que ambos casos son mucho más lentos que una función similar a strtok.

    – Tomás

    1 de marzo de 2009 a las 16:51

  • Y para aquellos que aún no tienen boost… bcp copia más de 1000 archivos para esto 🙂

    – Román Starkov

    9 de junio de 2010 a las 20:12

  • Advertencia, cuando se le da una cadena vacía (“”), este método devuelve un vector que contiene la cadena “”. Así que agregue un “if (!string_to_split.empty())” antes de la división.

    – Ofrecimiento

    11/10/2011 a las 13:10

  • No todos los desarrolladores de @Ian Embedded usan boost.

    – ACK_stoverflow

    31 de enero de 2012 a las 18:23

  • como complemento: uso boost solo cuando debo, normalmente prefiero agregar a mi propia biblioteca de código que es independiente y portátil para que pueda lograr un código pequeño, preciso y específico, que logre un objetivo determinado. De esa forma, el código no es público, tiene buen rendimiento, es trivial y es portátil. Boost tiene su lugar, pero sugeriría que es un poco exagerado para tokenizar cadenas: no tendrías que transportar toda tu casa a una empresa de ingeniería para clavar un nuevo clavo en la pared para colgar un cuadro… ellos pueden hacerlo. extremadamente bien, pero los pros son superados con creces por los contras.

    – GMasucci

    22 de mayo de 2013 a las 8:19


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

  • También puede dividir en otros delimitadores si usa getline en el while condición, por ejemplo, dividir por comas, usar while(getline(ss, buff, ',')).

    – Alí

    06/10/2018 a las 20:20

Para aquellos a quienes no les sienta bien sacrificar toda la eficiencia por el tamaño del código y ver “eficiente” como un tipo de elegancia, lo siguiente debería dar en el clavo (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;
   }
}

Normalmente 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 no se necesita acceso directo, e incluso puede crear su propia clase de cadena y usar algo como std::list<subString> donde subString no hace ninguna copia para aumentos de velocidad increíbles.

Es más del doble de rápido que el tokenize más rápido de 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 copias de cadenas y listas para aumentar la velocidad adicional.

Además, no realiza la devolución del resultado (extremadamente ineficiente), sino que pasa los tokens como referencia, lo que también le permite crear tokens utilizando múltiples llamadas si así lo desea.

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

Todo lo que necesita es std::string… el resto son opcionales. No utiliza secuencias ni la biblioteca boost, pero es lo suficientemente flexible como para poder aceptar algunos de estos tipos extraños de forma natural.

  • También puede dividir en otros delimitadores si usa getline en el while condición, por ejemplo, dividir por comas, usar while(getline(ss, buff, ',')).

    – Alí

    06/10/2018 a las 20:20

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

Se puede crear fácilmente una plantilla para manejar separadores de cadenas, cadenas anchas, etc.

Tenga en cuenta que dividir "" da como resultado una sola cadena vacía y la 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 se desea dividir una cadena en múltiples delimitadores mientras se saltan los tokens vacíos, se puede usar esta versión:

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

  • La primera versión es simple y hace el trabajo perfectamente. El único cambio que haría sería devolver el resultado directamente, en lugar de pasarlo como parámetro.

    – gregschlom

    19 de enero de 2012 a las 2:25

  • La salida se pasa como un parámetro para la eficiencia. Si se devolviera el resultado, se requeriría una copia del vector o una asignación de montón que luego tendría que liberarse.

    – Alec Thomas

    6 de febrero de 2012 a las 18:56

  • @AlecThomas: incluso antes de C ++ 11, ¿no optimizarían la mayoría de los compiladores la copia de retorno a través de NRVO? (+1 de todos modos; muy breve)

    – Marcelo Cantos

    17 de agosto de 2013 a las 11:54


  • De todas las respuestas, esta parece ser una de las más atractivas y flexibles. Junto con getline con un delimitador, aunque es una solución menos obvia. ¿El estándar c ++ 11 no tiene nada para esto? ¿C++ 11 admite tarjetas perforadas en estos días?

    – Space Jasset

    11 de agosto de 2015 a las 15:15


  • Sugiera usar std::string::size_type en lugar de int, ya que, de lo contrario, algunos compiladores podrían emitir advertencias firmadas/no firmadas.

    – Pascal Kesseli

    1 de noviembre de 2015 a las 20:45

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad