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

2988

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 en este momento 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 alguna forma más elegante de hacer esto?

Ashwin Nanjappa
fuente
617
Amigo ... La elegancia es solo una forma elegante de decir "eficiencia que se ve bonita" en mi libro. No rehuya el uso de funciones C y métodos rápidos para lograr cualquier cosa solo porque no está contenida en una plantilla;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon
21
@Eduardo: eso también está mal ... necesitas probar entre tratar de transmitir otro valor y usar ese valor, es decirstring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
99
Varias opciones en C ++ para hacer esto de forma predeterminada: cplusplus.com/faq/sequences/strings/split
hB0
14
La elegancia es mucho más que una simple eficiencia. Los atributos elegantes incluyen bajo recuento de líneas y alta legibilidad. En mi humilde opinión, Elegance no es un proxy de eficiencia sino de mantenibilidad.
Matt

Respuestas:

1369

Para lo que vale, aquí hay otra forma de extraer tokens de una cadena de entrada, confiando solo 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 en una secuencia de salida, uno podría insertarlos 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 vectordirectamente:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
fuente
164
¿Es posible especificar un delimitador para esto? ¿Como, por ejemplo, dividirse en comas?
l3dx el
15
@ Jonathan: \ n no es el delimitador en este caso, es el delimitador para la salida a cout.
huy
772
Esta es una solución pobre ya que no toma ningún otro delimitador, por lo tanto, no es escalable ni mantenible.
HelloWorld
37
En realidad, esto puede funcionar bien con otros delimitadores (aunque hacer algunos es algo feo). Cree una faceta ctype que clasifique los delimitadores deseados como espacios en blanco, cree una configuración regional que contenga esa faceta y luego imbuya la secuencia de cadenas con esa configuración regional antes de extraer las cadenas.
Jerry Coffin
53
@Kinderchocolate "Se puede suponer que la cadena está compuesta de palabras separadas por espacios en blanco" - Hmm, no parece una solución pobre al problema de la pregunta. "no escalable y no mantenible" - Ja , bueno.
Christian Rau
2426

Lo uso 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::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
fuente
86
Para evitar que salte tokens vacíos, empty()verifique:if (!item.empty()) elems.push_back(item)
0x499602D2
11
¿Qué tal el delim contiene dos caracteres como ->?
herohuyongtao
77
@herohuyongtao, esta solución solo funciona para delimitadores de caracteres individuales.
Evan Teran
44
@JeshwanthKumarNK, no es necesario, pero le permite hacer cosas como pasar el resultado directamente a una función como esta: f(split(s, d, v))sin dejar de tener el beneficio de una preasignación vectorsi lo desea.
Evan Teran
8
Advertencia: split ("one: two :: three", ':') y split ("one: two :: three:", ':') devuelven el mismo valor.
dshin
834

Una posible solución con 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 dado que esta es una función de plantilla genérica, puede usarse para dividir otros tipos de cadenas (wchar, etc. o UTF-8) usando todo tipo de delimitadores.

Consulte la documentación para más detalles.

ididak
fuente
35
La velocidad es irrelevante aquí, ya que ambos casos son mucho más lentos que una función similar a strtok.
Tom
45
Y para aquellos que aún no tienen impulso ... bcp copia más de 1,000 archivos para esto :)
Roman Starkov
12
Advertencia, cuando se le da una cadena vacía (""), este método devuelve un vector que contiene la cadena "". Entonces agregue un "if (! String_to_split.empty ())" antes de la división.
Offirmo
29
Los desarrolladores de @Ian Embedded no usan todos impulso.
ACK_stoverflow
31
como un apéndice: utilizo boost solo cuando debo hacerlo, normalmente prefiero agregar a mi propia biblioteca de código que es independiente y portátil para poder lograr un código específico pequeño y preciso, que cumple un objetivo determinado. De esa forma, el código no es público, tiene un rendimiento, es trivial y portátil. Boost tiene su lugar, pero sugeriría que es un poco excesivo para las cadenas de token: no harías que toda tu casa fuera transportada a una empresa de ingeniería para que clavaran un nuevo clavo en la pared para colgar una foto ... pueden hacerlo. extremadamente bien, pero los pros superan con creces a los contras.
GMasucci
364
#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
fuente
13
También puede dividir en otros delimitadores si usa getlineen la whilecondición, por ejemplo, para dividir por comas, use while(getline(ss, buff, ',')).
Ali
181

Para aquellos con quienes no se siente bien sacrificar toda la eficiencia por el tamaño del código y ver lo "eficiente" como un tipo de elegancia, lo siguiente debería llegar a un punto óptimo (y creo que la clase de contenedor de plantillas es una adición increíblemente 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<>cuando no se necesita acceso directo, e incluso puede crear su propia clase de cadena y usar algo como std::list<subString>dondesubString no hace ninguna copia a una velocidad increíble aumenta

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

Además, no hace el retorno (extremadamente ineficiente) del resultado, sino que pasa los tokens como referencia, lo que también le permite construir tokens utilizando múltiples llamadas si así lo desea.

Por último, le permite especificar si se recortarán 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 de impulso, pero es lo suficientemente flexible como para poder aceptar algunos de estos tipos foráneos de forma natural.

Marius
fuente
55
Soy un gran admirador de esto, pero para g ++ (y probablemente una buena práctica) cualquiera que use esto querrá typedefs y typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Luego, sustituya value_type y size_types en consecuencia.
aws
11
Para aquellos de nosotros para quienes las cosas de la plantilla y el primer comentario son completamente extraños, un ejemplo de uso completo con las inclusiones requeridas sería maravilloso.
Wes Miller
3
Ahh bien, lo descubrí. Puse las líneas C ++ del comentario de aws dentro del cuerpo de la función de tokenize (), luego edité las líneas tokens.push_back () para cambiar ContainerT :: value_type solo a ValueType y cambié (ContainerT :: value_type :: size_type) a ( Tipo de letra). Se corrigieron los bits por los que g ++ había estado quejándose. Simplemente invoca como tokenize (some_string, some_vector);
Wes Miller
2
Además de ejecutar algunas pruebas de rendimiento en datos de muestra, principalmente lo reduje a la menor cantidad posible de instrucciones y también a la menor cantidad posible de copias de memoria habilitadas mediante el uso de una clase de subcadena que solo hace referencia a desplazamientos / longitudes en otras cadenas. (Rodé la mía, pero hay algunas otras implementaciones). Desafortunadamente, no hay mucho más que uno pueda hacer para mejorar esto, pero fueron posibles incrementos incrementales.
Marius
3
Esa es la salida correcta para cuándo trimEmpty = true. Tenga en cuenta que "abo"no es un delimitador en esta respuesta, sino la lista de caracteres delimitadores. Sería simple modificarlo para tomar una sola cadena de caracteres delimitadores (creo que str.find_first_ofdebería cambiar a str.find_first, pero podría estar equivocado ... no puedo probar)
Marius
158

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 templar fácilmente para manejar separadores de cuerdas, cadenas anchas, etc.

Tenga en cuenta que la división ""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 varios delimitadores mientras se omiten 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;
}
Alec Thomas
fuente
10
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 un parámetro.
gregschlom
2
La salida se pasa como un parámetro para la eficiencia. Si el resultado fuera devuelto, requeriría una copia del vector o una asignación de montón que luego tendría que ser liberada.
Alec Thomas
2
Una ligera adición a mi comentario anterior: esta función podría devolver el vector sin penalización si se usa la semántica de movimiento C ++ 11.
Alec Thomas
77
@AlecThomas: Incluso antes de C ++ 11, ¿la mayoría de los compiladores no optimizarían la copia de devolución a través de NRVO? (+1 de todos modos; muy sucinto)
Marcelo Cantos
11
De todas las respuestas, esta parece ser una de las más atractivas y flexibles. Junto con el 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?
Spacen Jasset
123

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...
}
gnomed
fuente
¿Es posible declarar wordcomo a char?
abatishchev
Lo siento abatishchev, C ++ no es mi punto fuerte. Pero imagino que no sería difícil agregar un bucle interno para recorrer cada carácter en cada palabra. Pero en este momento creo que el ciclo actual depende de espacios para la separación de palabras. A menos que sepa que solo hay un único carácter entre cada espacio, en cuyo caso puede simplemente lanzar "palabra" a un personaje ... lo siento, no puedo ser de más ayuda, he tenido la intención de
repasar
11
si declara word como char, iterará sobre cada carácter que no sea un espacio en blanco. Es bastante simple de probar:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Esto es similar a la pregunta de desbordamiento de pila ¿ Cómo tokenizo 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;
    }
}
Ferruccio
fuente
¿Esto materializa una copia de todos los tokens, o solo mantiene la posición inicial y final del token actual?
einpoklum
66

Me gusta lo siguiente porque pone los resultados en un vector, admite una cadena como delimitación y da control sobre el mantenimiento de 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()que funciona parcialmente de esa manera. Y, si por 'espacio en blanco', realmente se refiere a cualquier tipo de espacio en blanco, usar la división de Boost con is_any_of()funciona muy bien.

Shadow2531
fuente
Finalmente, una solución que maneja los tokens vacíos correctamente a ambos lados de la cadena
fmuecke
53

El STL no tiene dicho método disponible ya.

Sin embargo, puede usar la strtok()función de C utilizando el std::string::c_str()miembro, o puede escribir la suya propia. Aquí hay una muestra de código que encontré después de una búsqueda rápida en Google ( "división de cadena STL" ):

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 el ejemplo de código, deje un comentario y se lo explicaré.

Y solo porque no implemente un typedefiterador llamado o sobrecarga el <<operador no significa que sea un código incorrecto. Yo uso las funciones de C con bastante frecuencia. Por ejemplo, printfy scanfambos son más rápidos que ( std::ciny std::coutsignificativamente), la fopensintaxis es mucho más amigable para los tipos binarios, y también tienden a producir EXE más pequeños.

No se deje vender en este acuerdo de "Elegancia sobre rendimiento" .

usuario19302
fuente
Soy consciente de las funciones de la cadena C y también de los problemas de rendimiento (que he notado en mi pregunta). Sin embargo, para esta pregunta específica, estoy buscando una solución elegante de C ++.
Ashwin Nanjappa
11
@ Nelson LaQuet: Déjame adivinar: ¿porque strtok no es reentrante?
paercebal
40
¡@Nelson nunca pase string.c_str () a strtok! strtok tira la cadena de entrada (inserta caracteres '\ 0' para reemplazar cada delimitador foudn) y c_str () devuelve una cadena no modificable.
Evan Teran
3
@ Nelson: Esa matriz debe ser de tamaño str.size () + 1 en su último comentario. Pero estoy de acuerdo con su tesis de que es una tontería evitar las funciones C por razones "estéticas".
j_random_hacker
2
@paulm: No, la lentitud de las transmisiones de C ++ es causada por facetas. Todavía son más lentos que las funciones stdio.h incluso cuando la sincronización está deshabilitada (y en cadenas de secuencias, que no pueden sincronizarse).
Ben Voigt
42

Aquí hay una función dividida que:

  • es genérico
  • utiliza C ++ estándar (sin impulso)
  • acepta múltiples delimitadores
  • ignora los tokens vacíos (se pueden cambiar fácilmente)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Ejemplo de uso:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
fuente
Olvidó agregar a la lista de uso: "extremadamente ineficiente"
Xander Tulip
1
@XanderTulip, ¿puedes ser más constructivo y explicar cómo o por qué?
Marco M.
3
@XanderTulip: supongo que te estás refiriendo a que devuelve el vector por valor. La optimización del valor de retorno (RVO, google it) debería encargarse de esto. También en C ++ 11 puede regresar por referencia de movimiento.
Joseph Garvin,
3
Esto puede optimizarse aún más: en lugar de .push_back (str.substr (...)) se puede usar .emplace_back (str, start, pos - start). De esta forma, el objeto de cadena se construye en el contenedor y, por lo tanto, evitamos una operación de movimiento + otras travesuras realizadas por la función .substr.
Mihai Bişog
@zoopp sí. Buena idea. VS10 no tenía soporte emplace_back cuando escribí esto. Actualizaré mi respuesta. Gracias
Marco M.
36

Tengo una solución de 2 líneas para este problema:

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;

Luego, en lugar de imprimir, puede ponerlo en un vector.

rhomu
fuente
35

Otra forma flexible y rápida.

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Para usarlo con un vector de cadenas (Editar: ya que alguien señaló que no heredaría las clases STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

¡Eso es! Y esa es solo una forma de usar el tokenizador, como cómo contar palabras:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Limitado por la imaginación;)

Robert
fuente
Agradable. Con respecto a la Appendernota "¿Por qué no deberíamos heredar una clase de las clases STL?"
Andreas Spindler
32

Aquí hay una solución simple que usa solo la biblioteca de expresiones regulares estándar

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

El argumento regex permite verificar múltiples argumentos (espacios, comas, etc.)

Por lo general, solo verifico la división en espacios y comas, por lo que también tengo esta función predeterminada:

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

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

    return Tokenize( str, re );
}

Las "[\\s,]+"comprobaciones de espacios ( \\s) y comas (, ).

Tenga en cuenta que si desea dividir en wstringlugar de string,

  • cambiar todo std::regexastd::wregex
  • cambiar todo sregex_token_iteratorawsregex_token_iterator

Tenga en cuenta que también puede tomar el argumento de cadena por referencia, dependiendo de su compilador.

dk123
fuente
Esta habría sido mi respuesta favorita, pero std :: regex está roto en GCC 4.8. Dijeron que lo implementaron correctamente en GCC 4.9. Todavía te estoy dando mi +1
mchiasson
1
Este es mi favorito con pequeños cambios: el vector devuelto como referencia como dijiste, y los argumentos "str" ​​y "regex" pasaron también por referencias. gracias.
QuantumKarl
1
Las cadenas sin formato son bastante útiles cuando se trata de patrones de expresiones regulares. De esa manera, no tiene que usar las secuencias de escape ... Simplemente puede usar R"([\s,]+)".
Sam
26

Usarlo std::stringstreamcomo lo hace funciona perfectamente bien y hacer exactamente lo que quería. Sin embargo, si solo está buscando una forma diferente de hacer las cosas, puede usar std::find()/ std::find_first_of()y std::string::substr().

Aquí hay un ejemplo:

#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;
}
KTC
fuente
Esto solo funciona para delimitadores de un solo carácter. Un cambio simple le permite trabajar con múltiples personajes:prev_pos = pos += delimiter.length();
David Doria
25

Si desea usar boost, pero desea usar una cadena completa como delimitador (en lugar de caracteres individuales como en la mayoría de las soluciones propuestas anteriormente), puede usar el boost_split_iterator.

Código de ejemplo que incluye una plantilla conveniente:

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

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}
zerm
fuente
20

Aquí hay una solución de expresiones regulares que solo utiliza la biblioteca de expresiones regulares estándar. (Estoy un poco oxidado, por lo que puede haber algunos errores de sintaxis, pero esta es al menos la idea general)

#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
}
AJMansfield
fuente
Respuestas similares con un enfoque quizás mejor de expresiones regulares: aquí y aquí .
nobar
20

Hay una función llamada strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}
Pratik Deoghare
fuente
3
strtokes de la biblioteca estándar de C, no de C ++. No es seguro usarlo en programas multiproceso. Modifica la cadena de entrada.
Kevin Panko
13
Debido a que almacena el puntero de caracteres de la primera llamada en una variable estática, de modo que en las llamadas posteriores cuando se pasa NULL, recuerda qué puntero debe usarse. Si un segundo subproceso llama strtokcuando otro subproceso aún se está procesando, este puntero de caracteres se sobrescribirá y ambos subprocesos tendrán resultados incorrectos. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
como se mencionó antes, strtok es inseguro e incluso en C se recomienda usar
strtok_r
44
strtok_r puede usarse si está en una sección de código a la que se puede acceder. esta es la única solución de todo lo anterior que no es "ruido de línea", y es un testimonio de lo que, exactamente, está mal con c ++
Erik Aronesty
Actualizado para que no haya objeciones por razones de seguridad de subprocesos de C ++ wonks.
Erik Aronesty
17

El flujo de cadena puede ser conveniente si necesita analizar la cadena por símbolos que no sean espacios:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
lukmac
fuente
14

Hasta ahora utilicé el de Boost , pero necesitaba algo que no dependiera de él, así que llegué a esto:

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

Un buen punto es que separatorspuedes pasar más de un personaje.

Goran
fuente
13

He rodado el mío usando strtok y he usado boost para dividir una cadena. El mejor método que he encontrado es la Biblioteca de C ++ String Toolkit . Es increíblemente flexible y rápido.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
        std::string s("Somewhere down the road");
        std::vector<std::string> result;
        if( strtk::parse( s, whitespace, result ) )
        {
            for(size_t i = 0; i < result.size(); ++i )
                std::cout << result[i] << std::endl;
        }
    }

    {  // parsing a string into a vector of floats with other separators
        // besides spaces

        std::string s("3.0, 3.14; 4.0");
        std::vector<float> values;
        if( strtk::parse( s, whitespace_and_punctuation, values ) )
        {
            for(size_t i = 0; i < values.size(); ++i )
                std::cout << values[i] << std::endl;
        }
    }

    {  // parsing a string into specific variables

        std::string s("angle = 45; radius = 9.9");
        std::string w1, w2;
        float v1, v2;
        if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
        {
            std::cout << "word " << w1 << ", value " << v1 << std::endl;
            std::cout << "word " << w2 << ", value " << v2 << std::endl;
        }
    }

    return 0;
}

El kit de herramientas tiene mucha más flexibilidad de lo que muestra este simple ejemplo, pero su utilidad para analizar una cadena en elementos útiles es increíble.

DannyK
fuente
13

Corto y elegante

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

puede usar cualquier cadena como delimitador, también se puede usar con datos binarios (std :: string admite datos binarios, incluidos los nulos)

utilizando:

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

salida:

this
is
!example!string
usuario1438233
fuente
1
Me gusta esta solución porque permite que el separador sea una cadena y no un carácter, sin embargo, está modificando la cadena en su lugar, por lo que está obligando a la creación de una copia de la cadena original.
Alessandro Teruzzi
11

Hice esto porque necesitaba una manera fácil de dividir cadenas y cadenas basadas en c ... Espero que alguien más pueda encontrarlo útil también. Además, no se basa en tokens y puede usar campos como delimitadores, que es otra clave que necesitaba.

Estoy seguro de que se pueden hacer mejoras para mejorar aún más su elegancia y, por favor, háganlo por todos los medios.

StringSplitter.hpp:

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

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Ejemplos:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

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

    return 0;
}

Saldrá:

Este
es
un
ejemplo de
cstring

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

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

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

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

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

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

    return 0;
}

Para mantener entradas vacías (por defecto, los vacíos serán excluidos):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

El objetivo era hacerlo similar al método Split () de C # donde dividir una cadena es tan fácil como:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Espero que alguien más pueda encontrar esto tan útil como yo.

Steve Dell
fuente
10

¿Qué hay de esto?

#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;
}
gibbz
fuente
Esta es la mejor respuesta aquí, si solo desea dividir en un solo carácter delimitador. Sin embargo, la pregunta original quería dividirse en espacios en blanco, lo que significaba cualquier combinación de uno o más espacios o pestañas consecutivas. Realmente has respondido stackoverflow.com/questions/53849
Oktalist
10

Esta respuesta toma la cadena y la coloca en un vector de cadenas. Utiliza la biblioteca de impulso.

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

Aquí hay otra forma de hacerlo ...

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}
user246110
fuente
9

Me gusta utilizar los métodos boost / regex para esta tarea, ya que proporcionan la máxima flexibilidad para especificar los criterios de división.

#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;
}
Marty B
fuente
9

Recientemente tuve que dividir una palabra en camello en subpalabras. No hay delimitadores, solo caracteres superiores.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Por ejemplo, esto divide "AQueryTrades" en "A", "Query" y "Trades". La función funciona con cadenas estrechas y anchas. Debido a que respeta la ubicación actual, divide "RaumfahrtÜberwachungsVerordnung" en "Raumfahrt", "Überwachungs" y "Verordnung".

La nota std::upperrealmente se debe pasar como argumento de plantilla de función. Entonces, el más generalizado de esta función puede dividirse en delimitadores como ",", ";"o " "también.

Andreas Spindler
fuente
2
Ha habido 2 revs. Eso es bueno. Parece que mi inglés tenía mucho de un "alemán". Sin embargo, el revisionista no solucionó dos errores menores, tal vez porque eran obvios de todos modos: std::isupperpodría pasarse como argumento, no std::upper. Segundo puesto un typenameantes del String::const_iterator.
Andreas Spindler
9
#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;
}
san45
fuente
9

Usando std::string_viewy Eric Niebler'srange-v3 biblioteca de :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Al usar un forbucle de rango en lugar de un ranges::for_eachalgoritmo:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}
Porsche9II
fuente
Sí, el rango para la base se ve mejor - Estoy de acuerdo
Porsche9II