¿Cómo tokenizar una cadena en C ++?


414

Java tiene un método de división conveniente:

String str = "The quick brown fox";
String[] results = str.split(" ");

¿Hay una manera fácil de hacer esto en C ++?


172
No puedo creer que esta tarea de rutina es tal un dolor de cabeza en C ++
wfbarksdale

66
No es dolor de cabeza en c ++: hay varias formas de lograrlo. los programadores son menos conscientes de c ++ que c #: se trata de marketing e inversiones ... vea esto para varias opciones de c ++ para lograr lo mismo: cplusplus.com/faq/sequences/strings/split
hB0

99
@ hB0 pasar por muchas preguntas respuestas y aún no decidir los medios es un dolor de cabeza. uno necesita esa biblioteca, el otro es solo para espacios, el otro no maneja espacios ..
Paschalis

1
Posible duplicado de dividir una cadena en C ++?
KOB

2
¿Por qué todo en C ++ tiene que ser una lucha?
Wael Assaf

Respuestas:


145

Los algoritmos de biblioteca estándar de C ++ se basan bastante universalmente en iteradores en lugar de contenedores concretos. Desafortunadamente, esto hace que sea difícil proporcionar una splitfunción similar a Java en la biblioteca estándar de C ++, aunque nadie argumenta que esto sería conveniente. Pero, ¿cuál sería su tipo de retorno? std::vector<std::basic_string<…>>? Tal vez, pero luego nos vemos obligados a realizar asignaciones (potencialmente redundantes y costosas).

En cambio, C ++ ofrece una gran cantidad de formas de dividir cadenas basadas en delimitadores complejos arbitrariamente, pero ninguna de ellas está tan bien encapsulada como en otros lenguajes. Las numerosas formas llenan publicaciones de blog enteras .

En su forma más simple, puede iterar usando std::string::findhasta que golpee std::string::nposy extraer el contenido usando std::string::substr.

Una versión más fluida (e idiomática, pero básica) para dividir en espacios en blanco usaría std::istringstream:

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

Usando std::istream_iterators , el contenido de la secuencia de cadena también podría copiarse en un vector usando su constructor de rango de iterador.

Varias bibliotecas (como Boost.Tokenizer ) ofrecen tokenisers específicos.

La división más avanzada requiere expresiones regulares. C ++ proporciona el std::regex_token_iteratorpara este propósito en particular:

auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
    std::sregex_token_iterator{begin(str), end(str), re, -1},
    std::sregex_token_iterator{}
);

53
Lamentablemente, el impulso no siempre está disponible para todos los proyectos. Tendré que buscar una respuesta sin impulso.
FuzzyBunnySlippers

36
No todos los proyectos están abiertos al "código abierto". Trabajo en industrias muy reguladas. No es un problema, de verdad. Es solo un hecho de la vida. Boost no está disponible en todas partes.
FuzzyBunnySlippers

55
@NonlinearIdeas La otra pregunta / respuesta no era sobre proyectos de código abierto en absoluto. Lo mismo es cierto para cualquier proyecto. Dicho esto, por supuesto entiendo acerca de los estándares restringidos como MISRA C, pero luego se entiende que usted construye todo desde cero de todos modos (a menos que encuentre una biblioteca compatible, una rareza). De todos modos, el punto es que "Boost no está disponible", es que tiene requisitos especiales para los que casi cualquier respuesta de propósito general no sería adecuada.
Konrad Rudolph

1
@NonlinearIdeas Caso en cuestión, las otras respuestas que no son de Boost tampoco son compatibles con MISRA.
Konrad Rudolph el

3
@Dmitry ¿Qué es "STL barf"? Y toda la comunidad está muy a favor de reemplazar el preprocesador C; de hecho, hay propuestas para hacerlo. Pero su sugerencia de usar PHP u otro lenguaje sería un gran paso atrás.
Konrad Rudolph

188

La clase de tokenizer Boost puede hacer que este tipo de cosas sea bastante simple:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

Actualizado para C ++ 11:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) {
        cout << t << "." << endl;
    }
}

1
Buenas cosas, recientemente he utilizado esto. Mi compilador de Visual Studio tiene un toque extraño hasta que uso un espacio en blanco para separar los dos caracteres ">" antes del bit de tokens (texto, sep): (error C2947: esperando que '>' termine la lista de argumentos de plantilla, encontrado '> > ')
AndyUK

@AndyUK sí, sin el espacio, el compilador lo analiza como un operador de extracción en lugar de dos plantillas de cierre.
EnabrenTane

Teóricamente, eso se ha solucionado en C ++ 0x
David Souther

3
cuidado con los terceros parámetros del char_separatorconstructor ( drop_empty_tokenses el predeterminado, la alternativa es keep_empty_tokens).
Benoit

55
@puk: es un sufijo comúnmente utilizado para los archivos de encabezado C ++. (como .hpara los encabezados C)
Ferruccio

167

Aquí hay una muy simple:

#include <vector>
#include <string>
using namespace std;

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}

¿Necesito agregar un prototipo para este método en el archivo .h?
Suhrob Samiev

55
Esta no es exactamente la "mejor" respuesta, ya que todavía utiliza un literal de cadena que es la matriz de caracteres constantes en C. Creo que el interrogador preguntaba si podía tokenizar una cadena de C ++ que es del tipo "cadena" introducida por este último.
Vijay Kumar Kanta

Esto necesita una nueva respuesta porque sospecho fuertemente que la inclusión de expresiones regulares en C ++ 11 ha cambiado cuál sería la mejor respuesta.
Omnifarious

114

Usa strtok. En mi opinión, no es necesario crear una clase sobre tokenización a menos que strtok no le proporcione lo que necesita. Puede que no, pero en más de 15 años de escribir varios códigos de análisis en C y C ++, siempre he usado strtok. Aquí hay un ejemplo

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

Algunas advertencias (que pueden no satisfacer sus necesidades). La cadena se "destruye" en el proceso, lo que significa que los caracteres EOS se colocan en línea en los puntos delimitadores. El uso correcto puede requerir que haga una versión no constante de la cadena. También puede cambiar la lista de delimitadores a mitad de análisis.

En mi propia opinión, el código anterior es mucho más simple y fácil de usar que escribir una clase separada para él. Para mí, esta es una de esas funciones que proporciona el lenguaje y lo hace bien y de manera limpia. Es simplemente una solución "basada en C". Es apropiado, es fácil y no tiene que escribir mucho código extra :-)


42
No es que no me guste C, sin embargo, strtok no es seguro para subprocesos, y debe asegurarse de que la cadena que envía contiene un carácter nulo para evitar un posible desbordamiento del búfer.
tloach

11
Hay strtok_r, pero esta fue una pregunta de C ++.
contrato del Prof. Falken incumplió el

3
@tloach: en el compilador MS C ++ strtok es seguro para subprocesos ya que la variable estática interna se crea en el TLS (almacenamiento local de subprocesos) (en realidad depende del compilador)
Ahmed dijo el

3
@ahmed: thread safe significa más que solo poder ejecutar la función dos veces en diferentes hilos. En este caso, si el subproceso se modifica mientras se ejecuta strtok, es posible que la cadena sea válida durante toda la ejecución de strtok, pero strtok seguirá en mal estado porque la cadena cambió, ahora ya pasó el carácter nulo y va a siga leyendo la memoria hasta que obtenga una violación de seguridad o encuentre un carácter nulo. Este es un problema con las funciones de cadena C originales, si no especifica una longitud en algún lugar donde tenga problemas.
tloach

44
strtok requiere un puntero a una matriz de caracteres con terminación nula no constante, que no es una criatura común para encontrar en el código c ++ ... ¿cuál es su forma favorita de convertir esto desde una cadena std ::?
fuzzyTew

106

Otra forma rápida es usar getline. Algo como:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

Si lo desea, puede hacer que un split()método simple devuelva a vector<string>, lo cual es realmente útil.


2
Tuve problemas al usar esta técnica con caracteres 0x0A en la cadena que hizo que el ciclo while saliera prematuramente. De lo contrario, es una buena solución simple y rápida.
Ryan H.

44
Esto es bueno, pero solo hay que tener en cuenta que al hacerlo no se considera el delimitador predeterminado '\ n'. Este ejemplo funcionará, pero si está usando algo como: while (getline (inFile, word, '')) donde inFile es un objeto ifstream que contiene varias líneas obtendrá resultados divertidos ...
hackrock

es una lástima que getline devuelva la secuencia en lugar de la cadena, por lo que es inutilizable en las listas de inicialización sin almacenamiento temporal
fuzzyTew

1
¡Frio! Sin impulso y C ++ 11, ¡buena solución para los proyectos heredados que existen!
Deqing

1
Esa es la respuesta, el nombre de la función es un poco incómodo.
Nils

82

Puede usar secuencias, iteradores y el algoritmo de copia para hacer esto de manera bastante directa.

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()
{
  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);
}

17
Encuentro esos std :: irritantes de leer ... ¿por qué no usar "usar"?
user35978 el

80
@Vadi: porque editar la publicación de otra persona es bastante intrusivo. @pheze: prefiero dejar que de stdesta manera sepa de dónde viene mi objeto, eso es simplemente una cuestión de estilo.
Matthieu M.

77
Entiendo tu razón y creo que en realidad es una buena opción si te funciona, pero desde un punto de vista pedagógico, estoy de acuerdo con pheze. Es más fácil leer y comprender un ejemplo completamente extraño como este con un "uso de std de espacio de nombres" en la parte superior porque requiere menos esfuerzo para interpretar las siguientes líneas ... especialmente en este caso porque todo es de la biblioteca estándar. Puede hacer que sea fácil de leer y obvio de dónde provienen los objetos mediante una serie de "usando std :: string;" etc. Especialmente porque la función es muy corta.
cheshirekow

61
A pesar de que los prefijos "std ::" son irritantes o feos, es mejor incluirlos en el código de ejemplo para que quede completamente claro de dónde provienen estas funciones. Si te molestan, es trivial reemplazarlos con un "uso" después de robar el ejemplo y reclamarlo como tuyo.
dlchambers

20
¡Sí! ¡lo que dijo! Las mejores prácticas son utilizar el prefijo estándar. Sin duda, cualquier base de código grande tendrá sus propias bibliotecas y espacios de nombres, y el uso de "usando el espacio de nombres estándar" le dará dolores de cabeza cuando comience a causar conflictos en el espacio de nombres.
Miek

48

No hay gente ofender, pero para un problema tan simple, se están haciendo las cosas manera demasiado complicado. Hay muchas razones para usar Boost . Pero para algo tan simple, es como golpear una mosca con un trineo de 20 #.

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

Por ejemplo (para el caso de Doug),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

Y sí, podríamos haber dividido () devolver un nuevo vector en lugar de pasar uno. Es trivial envolver y sobrecargar. Pero dependiendo de lo que estoy haciendo, a menudo me parece mejor reutilizar objetos preexistentes en lugar de crear siempre nuevos. (¡Siempre y cuando no me olvide de vaciar el vector en el medio!)

Referencia: http://www.cplusplus.com/reference/string/string/ .

(Originalmente estaba escribiendo una respuesta a la pregunta de Doug: Modificación y extracción de cadenas C ++ basadas en separadores (cerrado) . Pero como Martin York cerró esa pregunta con un puntero aquí ... simplemente generalizaré mi código).


12
¿Por qué definir una macro que solo usa en un lugar? ¿Y cómo es su UASSERT mejor que la afirmación estándar? Dividir la comparación en 3 tokens de esa manera no hace más que requerir más comas de las que de otro modo necesitarías.
crelbor

1
¿Quizás la macro UASSERT muestra (en el mensaje de error) la relación real entre (y los valores de) los dos valores comparados? Esa es realmente una muy buena idea, en mi humilde opinión.
GhassanPL

10
Ugh, ¿por qué la std::stringclase no incluye una función split ()?
Sr. Shickadance

Creo que la última línea en el ciclo while debería ser start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());y el ciclo while debería ser while (start != string::npos). Además, verifico la subcadena para asegurarme de que no esté vacía antes de insertarla en el vector.
John K

@JohnK Si la entrada tiene dos delimitadores consecutivos, entonces claramente la cadena entre ellos está vacía y debe insertarse en el vector. Si los valores vacíos no son aceptables para un propósito particular, eso es otra cosa, pero en mi humilde opinión, tales restricciones deben aplicarse fuera de este tipo de funciones de propósito muy general.
Lauri Nurmi

46

Una solución usando regex_token_iterators:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}

55
Esta debería ser la respuesta mejor clasificada. Esta es la manera correcta de hacer esto en C ++> = 11.
Omnifarious

1
Me alegro de haberme desplazado hasta esta respuesta (actualmente solo tenía 9 votos a favor). ¡Esto es exactamente lo que debería ser un código C ++ 11 para esta tarea!
YePhIcK

Excelente respuesta que no depende de bibliotecas externas y utiliza bibliotecas ya disponibles
Andrew

1
Gran respuesta, dando la mayor flexibilidad en delimitadores. Algunas advertencias: el uso de \ s + regex evita los tokens vacíos en el medio del texto, pero da un primer token vacío si el texto comienza con espacios en blanco. Además, la expresión regular parece lenta: en mi computadora portátil, para 20 MB de texto aleatorio, lleva 0.6 segundos, en comparación con 0.014 segundos para strtok, strsep o la respuesta de Parham usando str.find_first_of, o 0.027 segundos para Perl, o 0.021 segundos para Python . Para texto corto, la velocidad puede no ser una preocupación.
Mark Gates

2
Ok, tal vez se ve bien, pero esto es claramente un uso excesivo de expresiones regulares. Razonable solo si no te importa el rendimiento.
Marek R

35

Boost tiene una fuerte función de división: boost :: Algoritmo :: Split .

Programa de muestra:

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

int main() {
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;
}

Salida:

"a"
"b"
" c "
""
"e"
"f"
""

26

Sé que solicitó una solución C ++, pero podría considerar esto útil:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

La ventaja sobre Boost en este ejemplo es que es una asignación directa uno a uno al código de su publicación.

Ver más en la documentación de Qt


22

Aquí hay una clase de tokenizer de muestra que podría hacer lo que quieras

//Header file
class Tokenizer 
{
    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
};

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) {}

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) {}

bool Tokenizer::NextToken() 
{
    return NextToken(m_delimiters);
}

bool Tokenizer::NextToken(const std::string& delimiters) 
{
    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    {
        m_offset = m_string.length();
        return false;
    }

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    {
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    }

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;
}

Ejemplo:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}

19

Esta es una solución simple de solo STL (¡~ 5 líneas!) Que utiliza std::findy std::find_first_not_ofque maneja repeticiones del delimitador (como espacios o puntos, por ejemplo), así como delimitadores iniciales y finales:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}

Pruébalo en vivo !


3
Esta es buena, pero creo que debe usar find_first_of () en lugar de find () para que esto funcione correctamente con múltiples delimitadores.

2
@ user755921 se omiten varios delimitadores al encontrar la posición de inicio con find_first_not_of.
Principiante

16

pystring es una pequeña biblioteca que implementa un montón de funciones de cadena de Python, incluido el método de división:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");

3
Wow, has respondido mi pregunta inmediata y muchas preguntas futuras. Entiendo que c ++ es poderoso. Pero al dividir una cadena da como resultado un código fuente como las respuestas anteriores, es simplemente desalentador. Me encantaría saber de otras bibliotecas como esta que reducen las comodidades de los idiomas de nivel superior.
Ross

wow, en serio acabas de alegrarme el día !! No sabía sobre pystring. ¡Esto me va a ahorrar mucho tiempo!
Accraze

11

Publiqué esta respuesta para una pregunta similar.
No reinventes la rueda. He usado varias bibliotecas y la más rápida y flexible que he encontrado es: C ++ String Toolkit Library .

Aquí hay un ejemplo de cómo usarlo que he publicado en otro lugar en el stackoverflow.

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

8

Mira este ejemplo. Podría ayudarte ...

#include <iostream>
#include <sstream>

using namespace std;

int main ()
{
    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) {
        is >> tmps;
        cout << tmps << "\n";
    }
    return 0;
}

1
Lo haríawhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix

6

MFC / ATL tiene un tokenizador muy bueno. De MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
};

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third

1
Esta función Tokenize () omitirá los tokens vacíos, por ejemplo, si hay una subcadena "%%" en la cadena principal, no se devuelve ningún token vacío. Se omite
Brillo

4

Si está dispuesto a usar C, puede usar la función strtok . Debe prestar atención a los problemas de subprocesos múltiples al usarlo.


3
Tenga en cuenta que strtok modifica la cadena que está comprobando, por lo que no puede usarla en cadenas const char * sin hacer una copia.
Graeme Perrow

99
El problema de subprocesos múltiples es que strtok utiliza una variable global para realizar un seguimiento de dónde está, por lo que si tiene dos hilos que utilizan strtok, obtendrá un comportamiento indefinido.
JohnMcG

@JohnMcG O simplemente use strtok_sque es básicamente strtokcon paso explícito de estado.
Matthias

4

Para cosas simples, solo uso lo siguiente:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)
{
    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    {
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        {
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        }

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    }

    if (prev_pos < i_source.length())
    {
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    }

    return number_of_tokens;
}

Descargo de responsabilidad cobarde: escribo software de procesamiento de datos en tiempo real donde los datos ingresan a través de archivos binarios, sockets o alguna llamada de API (tarjetas de E / S, cámaras). Nunca uso esta función para algo más complicado o crítico en el tiempo que leer archivos de configuración externos al inicio.


4

Simplemente puede usar una biblioteca de expresiones regulares y resolver eso usando expresiones regulares.

Use la expresión (\ w +) y la variable en \ 1 (o $ 1 dependiendo de la implementación de la biblioteca de expresiones regulares).


+1 por sugerir expresiones regulares, si no necesita velocidad de deformación, es la solución más flexible, aún no se admite en todas partes, pero a medida que pase el tiempo, eso será menos importante.
odinthenerd

+1 de mi parte, acabo de intentar <regex> en c ++ 11. Tan simple y elegante
StahlRat

4

Muchas sugerencias demasiado complicadas aquí. Pruebe esta sencilla solución std :: string:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}

4

Pensé que para eso era el >>operador en secuencias de cadena:

string word; sin >> word;

1
Mi culpa por dar un mal ejemplo (demasiado simple). Hasta donde sé, eso solo funciona cuando su delimitador es un espacio en blanco.
Bill the Lizard el

4

La respuesta de Adam Pierce proporciona un tokenizador hilado a mano que toma a const char*. Es un poco más problemático hacerlo con los iteradores porque incrementar stringel iterador final de un no está definido . Dicho esto, dado string str{ "The quick brown fox" }que ciertamente podemos lograr esto:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Live Example


Si está buscando abstraer la complejidad mediante el uso de la funcionalidad estándar, como sugiere On Freund, strtok es una opción simple:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

Si no tiene acceso a C ++ 17, deberá sustituirlo data(str)como en este ejemplo: http://ideone.com/8kAGoa

Aunque no se demostró en el ejemplo, strtokno es necesario usar el mismo delimitador para cada token. Sin embargo, junto con esta ventaja, hay varios inconvenientes:

  1. strtokno puede ser utilizado en múltiples stringsal mismo tiempo: Ya sea un nullptrser sometidos a continuar tokenizar la corriente stringo un nuevo char*a tokenize debe pasar (hay algunas implementaciones no estándar que hacen apoyar esto, sin embargo, tales como: strtok_s)
  2. Por la misma razón strtok, no se puede usar en varios subprocesos simultáneamente (sin embargo, esto puede ser una implementación definida, por ejemplo: la implementación de Visual Studio es segura para subprocesos )
  3. La llamada strtokmodifica el estado stringen el que está operando, por lo que no se puede usar en const strings, const char*s o cadenas literales, para simular cualquiera de estos con strtoko para operar en stringel contenido de quién necesita ser preservado, strtendría que copiarse, entonces la copia podría ser operado en

nos proporciona split_viewtokenizar cadenas de manera no destructiva: https://topanswers.xyz/cplusplus?q=749#a874


Los métodos anteriores no pueden generar un vectorin situ tokenizado , es decir, sin abstraerlos en una función auxiliar que no puedan inicializar const vector<string> tokens. Esa funcionalidad y la capacidad de aceptar cualquier delimitador de espacios en blanco se pueden aprovechar mediante un istream_iterator. Por ejemplo dado: const string str{ "The quick \tbrown \nfox" }podemos hacer esto:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Live Example

La construcción requerida de un istringstreampara esta opción tiene un costo mucho mayor que las 2 opciones anteriores, sin embargo, este costo generalmente se oculta a expensas de la stringasignación.


Si ninguna de las opciones anteriores es lo suficientemente flexible para sus necesidades de tokenización, la opción más flexible es usar una, regex_token_iteratorpor supuesto, con esta flexibilidad conlleva un mayor gasto, pero nuevamente esto probablemente esté oculto en el stringcosto de asignación. Digamos, por ejemplo, que queremos tokenizar en base a comas no escapadas, también comiendo espacios en blanco, dada la siguiente entrada: const string str{ "The ,qu\\,ick ,\tbrown, fox" }podemos hacer esto:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Live Example


strtok_ses el estándar C11, por cierto. strtok_res un estándar POSIX2001. Entre ambos, hay una versión reentrante estándar strtokpara la mayoría de las plataformas.
Andon M. Coleman

@ AndonM.Coleman Pero esta es una pregunta de c ++ , y en C ++ #include <cstring>solo incluye la versión c99 de strtok. Entonces, ¿supongo que solo está proporcionando este comentario como material de apoyo, demostrando la disponibilidad específica de implementación de strtokextensiones?
Jonathan Mee

1
Simplemente que no es tan no estándar como la gente podría creer. strtok_ses proporcionado por C11 y como una extensión independiente en el tiempo de ejecución C de Microsoft. Hay un poco de historia curiosa aquí donde las _sfunciones de Microsoft se convirtieron en el estándar C.
Andon M. Coleman

@ AndonM.Coleman Correcto, estoy contigo. Obviamente, si está en el estándar C11, la interfaz y la implementación tienen restricciones que requieren un comportamiento idéntico independiente de la plataforma. Ahora el único problema es garantizar que la función C11 esté disponible para nosotros en todas las plataformas. Esperemos que el estándar C11 sea algo que C ++ 17 o C ++ 20 elija recoger.
Jonathan Mee

3

Sé que esta pregunta ya está respondida pero quiero contribuir. Tal vez mi solución es un poco simple, pero esto es lo que se me ocurrió:

vector<string> get_words(string const& text, string const& separator)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(separator);

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + separator.length());
        second_pos = tmp.find(separator);
    }

    result.push_back(tmp);

    return result;
}

Comente si hay un mejor enfoque para algo en mi código o si algo está mal.

ACTUALIZACIÓN: separador genérico agregado


Usó su solución de la multitud :) ¿Puedo modificar su código para agregar un separador?
Zac

1
@Zac me alegra que te haya gustado y ofc puedes modificarlo ... solo agrega una sección de actualización en negrita a mi respuesta ...
NutCracker

2

Aquí hay un enfoque que le permite controlar si los tokens vacíos están incluidos (como strsep) o excluidos (como strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}

2

Me parece extraño que con todos los nerds conscientes de la velocidad aquí en SO, nadie haya presentado una versión que use una tabla de búsqueda generada en tiempo de compilación para el delimitador (ejemplo de implementación más abajo). El uso de una tabla de búsqueda y los iteradores deberían vencer a std :: regex en eficiencia, si no necesita vencer a regex, simplemente utilícelo, su estándar a partir de C ++ 11 y súper flexible.

Algunos ya han sugerido expresiones regulares, pero para los novatos aquí hay un ejemplo empaquetado que debería hacer exactamente lo que el OP espera:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

Si necesitamos ser más rápidos y aceptar la restricción de que todos los caracteres deben ser de 8 bits, podemos hacer una tabla de búsqueda en tiempo de compilación usando metaprogramación:

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

Con eso en su lugar, hacer una getNextTokenfunción es fácil:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

Usarlo también es fácil:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

Aquí hay un ejemplo en vivo: http://ideone.com/GKtkLQ


1
¿Es posible tokenizar con un delimitador de cadena?
Galigator

esta versión solo está optimizada para delimitadores de un solo carácter, el uso de una tabla de búsqueda no es adecuado para delimitadores de múltiples caracteres (cadenas), por lo que es más difícil superar la expresión regular en eficiencia.
odinthenerd

1

puedes aprovechar boost :: make_find_iterator. Algo similar a esto:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}

1

Aquí está mi Swiss® Army Knife de tokenizadores de cadena para dividir cadenas por espacios en blanco, teniendo en cuenta las cadenas envueltas con comillas simples y dobles, así como eliminar esos caracteres de los resultados. Usé RegexBuddy 4.x para generar la mayor parte del fragmento de código, pero agregué un manejo personalizado para eliminar comillas y algunas otras cosas.

#include <string>
#include <locale>
#include <regex>

std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
    std::vector<std::wstring> tokens;

    std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);

    std::wsregex_iterator next( string_to_tokenize.begin(),
                                string_to_tokenize.end(),
                                re,
                                std::regex_constants::match_not_null );

    std::wsregex_iterator end;
    const wchar_t single_quote = L'\'';
    const wchar_t double_quote = L'\"';
    while ( next != end ) {
        std::wsmatch match = *next;
        const std::wstring token = match.str( 0 );
        next++;

        if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
            tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
        else
            tokens.emplace_back(token);
    }
    return tokens;
}

1
(Abajo) los votos pueden ser tan constructivos como los votos a favor, pero no cuando no dejas comentarios de por qué ...
kayleeFrye_onDeck

1
Te iguale, pero podría deberse a que el código parece bastante desalentador para el programador que busca en Google "cómo dividir una cadena", especialmente sin documentación
mattshu

Gracias @mattshu! ¿Son los segmentos de expresiones regulares lo que lo hace desalentador o algo más?
kayleeFrye_onDeck

0

Si se conoce la longitud máxima de la cadena de entrada que se tokenizará, se puede explotar esto e implementar una versión muy rápida. Estoy esbozando la idea básica a continuación, que se inspiró tanto en strtok () como en la estructura de datos de "matriz de sufijos" que se describe en la segunda edición, capítulo 15. "Perls de programación" de Jon Bentley, capítulo 15. La clase C ++ en este caso solo ofrece algo de organización y conveniencia de uso. La implementación que se muestra se puede ampliar fácilmente para eliminar los caracteres de espacio en blanco iniciales y finales en los tokens.

Básicamente, uno puede reemplazar los caracteres separadores con caracteres '\ 0' que terminan en cadena y establecer punteros a los tokens dentro de la cadena modificada. En el caso extremo cuando la cadena consiste solo en separadores, uno obtiene la longitud de la cadena más 1 fichas vacías resultantes. Es práctico duplicar la cadena a modificar.

Archivo de cabecera:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    {
        return mNumTokens;
    }

    const char *    GetToken( const size_t token_idx ) const
    {
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    }

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    {
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    }
};

Archivo de implementación:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )
{
    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();
}

TextLineSplitter::~TextLineSplitter()
{
    delete [] mBuff;
    delete [] mTokens;
}


void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )
{
    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    {
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        {
            mTokens[ mNumTokens ] = &mBuff[ idx ];
        } // if

        if( chr == sep_char || chr == '\0' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

Un escenario de uso sería:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
    printf( "%s\n", spl.GetToken( i ) );
}

salida:

Item1

Item2
Item3

0

boost::tokenizeres su amigo, pero considere hacer que su código sea portátil con referencia a problemas de internacionalización (i18n) usando wstring/ en wchar_tlugar de los string/ legacy / chartypes.

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()
{
  wstring s;
  while (getline(wcin, s)) {
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
      wcout << *beg << L"\t"; // output (or store in vector)
    }
    wcout << L"\n";
  }
  return 0;
}

"legacy" definitivamente no es correcto y wchar_tes un tipo horrible de implementación dependiente que nadie debería usar a menos que sea absolutamente necesario.
CoffeeandCode

El uso de wchar_t de alguna manera no resuelve automáticamente ningún problema de i18n. Usas codificaciones para resolver ese problema. Si está dividiendo una cadena por un delimitador, está implícito que el delimitador no colisiona con el contenido codificado de ningún token dentro de la cadena. Puede ser necesario escapar, etc. wchar_t no es una solución mágica para esto.
yonil

0

El código simple de C ++ (C ++ 98 estándar) acepta múltiples delimitadores (especificados en una cadena estándar ::), usa solo vectores, cadenas e iteradores.

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.