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

Resuelto Ashwin Nanjappa asked hace 16 años • 83 respuestas

¿Cómo itero sobre las palabras de una cadena compuesta de palabras separadas por espacios en blanco?

Tenga en cuenta que no estoy interesado en las funciones de cadena C ni en ese tipo de manipulación/acceso a caracteres. Prefiero la elegancia a la eficiencia. Mi solución actual:

#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);
}
Ashwin Nanjappa avatar Oct 25 '08 15:10 Ashwin Nanjappa
Aceptado

Lo uso para dividir una cadena por un delimitador. El primero coloca 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::istringstream iss(s);
    std::string item;
    while (std::getline(iss, 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 los tokens vacíos, por lo que a continuación encontrará 4 elementos, uno de los cuales está vacío:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran avatar Oct 25 '2008 18:10 Evan Teran

Por si sirve de algo, aquí hay otra forma de extraer tokens de una cadena de entrada, confiando únicamente en las instalaciones estándar de la biblioteca. 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, se podrían insertar en un contenedor, utilizando el mismo copyalgoritmo genérico.

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

... o crear vectordirectamente:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino avatar Oct 26 '2008 00:10 Zunino

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 stringstreamenfoque. Y como se trata de una función de plantilla genérica, se puede utilizar para dividir otros tipos de cadenas (wchar, etc. o UTF-8) utilizando todo tipo de delimitadores.

Consulte la documentación para obtener más detalles.

ididak avatar Oct 25 '2008 20:10 ididak
#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;
}
kev avatar Mar 06 '2011 05:03 kev

Una solución eficiente, pequeña y elegante que utiliza una función de plantilla:

template <class ContainerT>
void split(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.emplace_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<...>a veces es posible que sea preferible a vector<...>.

También le permite especificar si desea recortar tokens vacíos de los resultados mediante un último parámetro opcional.

Todo lo que requiere se std::stringincluye a través de <string>. No utiliza transmisiones ni la biblioteca boost explícitamente, pero podrá aceptar algunos de estos tipos.

Además, desde C++-17 puedes usar, std::vector<std::string_view>que es mucho más rápido y eficiente en memoria que usar std::string. Aquí hay una versión revisada que también admite el contenedor como tipo de devolución:

#include <vector>
#include <string_view>
#include <utility>
    
template < typename StringT,
           typename DelimiterT = char,
           typename ContainerT = std::vector<std::string_view> >
ContainerT split(StringT const& str, DelimiterT const& delimiters = ' ', bool trimEmpty = true, ContainerT&& tokens = {})
{
    typename StringT::size_type pos, lastPos = 0, length = str.length();

    while (lastPos < length + 1)
    {
        pos = str.find_first_of(delimiters, lastPos);
        if (pos == StringT::npos)
            pos = length;

      if (pos != lastPos || !trimEmpty)
            tokens.emplace_back(str.data() + lastPos, pos - lastPos);

        lastPos = pos + 1;
    }

    return std::forward<ContainerT>(tokens);
}

Se ha tenido cuidado de no hacer copias innecesarias.

Esto permitirá:

for (auto const& line : split(str, '\n'))

O:

auto& lines = split(str, '\n');

Ambos devuelven el tipo de contenedor de plantilla predeterminado de std::vector<std::string_view>.

Para recuperar un tipo de contenedor específico, o para pasar un contenedor existente, use el tokensparámetro de entrada con un contenedor inicial escrito o una variable de contenedor existente:

auto& lines = split(str, '\n', false, std::vector<std::string>());

O:

std::vector<std::string> lines;
split(str, '\n', false, lines);
Marius avatar Sep 29 '2009 15:09 Marius

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 cuerdas, cuerdas anchas, etc.

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

También se puede ampliar 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 y omitir 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;
}
Alec Thomas avatar Sep 13 '2011 20:09 Alec Thomas