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 ..
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:
autoconst str ="The quick brown fox"s;autoconst re = std::regex{R"(\s+)"};autoconst vec = std::vector<std::string>(
std::sregex_token_iterator{begin(str), end(str), re,-1},
std::sregex_token_iterator{});
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>usingnamespace std;usingnamespace 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>usingnamespace std;usingnamespace boost;int main(int,char**){
string text ="token, test string";
char_separator<char> sep(", ");
tokenizer<char_separator<char>> tokens(text, sep);for(constauto& t : tokens){
cout << t <<"."<< endl;}}
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>usingnamespace std;vector<string> split(constchar*str,char c =' '){vector<string> result;do{constchar*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 :-)
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 ::?
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);}
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) << "\"" << endlint
main(){vector<string> v;
split( v,"A:PEP:909:Inventory Item",":");for(unsignedint 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!)
¿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>usingnamespace 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;}}
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.
#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(constauto& field : fields)
std::cout <<"\""<< field <<"\"\n";return0;}
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);}}
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,"-");
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>constchar*whitespace =" \t\r\n\f";constchar*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;}}return0;}
#include<iostream>#include<sstream>usingnamespace std;int main (){
string tmps;
istringstream is ("the dellimiter is the space");while(is.good ()){
is >> tmps;
cout << tmps <<"\n";}return0;}
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.
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.
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.
+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
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:
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)
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
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:
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:
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ó:
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 versionreturn 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(constauto&s:v){
std::cout << s << std::endl;}
std::cout <<"crazy version:"<< std::endl;
v = split(str, std::regex{"[^e]+"});//using e as delim shows flexibilityfor(constauto&s:v){
std::cout << s << std::endl;}return0;}
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...>structBoolSequence{};//just here to hold boolstemplate<char...>structCharSequence{};//just here to hold charstemplate<typename T,char C>structContains;//generictemplate<charFirst,char...Cs,charMatch>//not first specializationstructContains<CharSequence<First,Cs...>,Match>:Contains<CharSequence<Cs...>,Match>{};//strip first and increase indextemplate<charFirst,char...Cs>//is first specializationstructContains<CharSequence<First,Cs...>,First>: std::true_type {};template<charMatch>//not found specializationstructContains<CharSequence<>,Match>: std::false_type{};template<int I,typename T,typename U>structMakeSequence;//generictemplate<int I,bool...Bs,typename U>structMakeSequence<I,BoolSequence<Bs...>, U>://not lastMakeSequence<I-1,BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};template<bool...Bs,typename U>structMakeSequence<0,BoolSequence<Bs...>,U>{//last usingType=BoolSequence<Bs...>;};template<typename T>structBoolASCIITable;template<bool...Bs>structBoolASCIITable<BoolSequence<Bs...>>{/* could be made constexpr but not yet supported by MSVC */staticbool isDelim(constchar c){staticconstbool table[256]={Bs...};return table[static_cast<int>(c)];}};usingDelims=CharSequence<'.',',',' ',':','\n'>;//list your custom delimiters hereusingTable=BoolASCIITable<typenameMakeSequence<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 endauto second = std::find_if(begin,end,Table{});//find first delim or endreturn 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;}return0;}
¿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>inlinevector< basic_string<CH>> tokenize(const basic_string<CH>&Input,const basic_string<CH>&Delimiter,bool remove_empty_token
){typedeftypename basic_string<CH>::const_iteratorstring_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()));}elseResult.push_back(basic_string<CH>(it,i->begin()));
it = i->end();}if(it != it_end)Result.push_back(basic_string<CH>(it,it_end));returnResult;}
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.
(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:
classTextLineSplitter{public:TextLineSplitter(constsize_t max_line_len );~TextLineSplitter();voidSplitLine(constchar*line,constchar sep_char =',',);inlinesize_tNumTokens(void)const{return mNumTokens;}constchar*GetToken(constsize_t token_idx )const{
assert( token_idx < mNumTokens );return mTokens[ token_idx ];}private:constsize_t mStorageSize;char*mBuff;char**mTokens;size_t mNumTokens;inlinevoidResetContent(void){
memset( mBuff,0, mStorageSize );// mark all items as empty:
memset( mTokens,0, mStorageSize *sizeof(char*));// reset counter for found items:
mNumTokens =0L;}};
// 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 ));}
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>usingnamespace std;usingnamespace 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 charactersTok 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";}return0;}
"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 tokenif(!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;}
Respuestas:
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
split
funció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::find
hasta que golpeestd::string::npos
y extraer el contenido usandostd::string::substr
.Una versión más fluida (e idiomática, pero básica) para dividir en espacios en blanco usaría
std::istringstream
:Usando
std::istream_iterator
s , 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_iterator
para este propósito en particular:fuente
La clase de tokenizer Boost puede hacer que este tipo de cosas sea bastante simple:
Actualizado para C ++ 11:
fuente
char_separator
constructor (drop_empty_tokens
es el predeterminado, la alternativa eskeep_empty_tokens
)..h
para los encabezados C)Aquí hay una muy simple:
fuente
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
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 :-)
fuente
Otra forma rápida es usar
getline
. Algo como:Si lo desea, puede hacer que un
split()
método simple devuelva avector<string>
, lo cual es realmente útil.fuente
Puede usar secuencias, iteradores y el algoritmo de copia para hacer esto de manera bastante directa.
fuente
std
esta manera sepa de dónde viene mi objeto, eso es simplemente una cuestión de estilo.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 #.
Por ejemplo (para el caso de Doug),
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).
fuente
std::string
clase no incluye una función split ()?start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
y el ciclo while debería serwhile (start != string::npos)
. Además, verifico la subcadena para asegurarme de que no esté vacía antes de insertarla en el vector.Una solución usando
regex_token_iterator
s:fuente
Boost tiene una fuerte función de división: boost :: Algoritmo :: Split .
Programa de muestra:
Salida:
fuente
Sé que solicitó una solución C ++, pero podría considerar esto útil:
Qt
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
fuente
Aquí hay una clase de tokenizer de muestra que podría hacer lo que quieras
Ejemplo:
fuente
Esta es una solución simple de solo STL (¡~ 5 líneas!) Que utiliza
std::find
ystd::find_first_not_of
que maneja repeticiones del delimitador (como espacios o puntos, por ejemplo), así como delimitadores iniciales y finales:Pruébalo en vivo !
fuente
pystring es una pequeña biblioteca que implementa un montón de funciones de cadena de Python, incluido el método de división:
fuente
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.
fuente
Mira este ejemplo. Podría ayudarte ...
fuente
while ( is >> tmps ) { std::cout << tmps << "\n"; }
MFC / ATL tiene un tokenizador muy bueno. De MSDN:
fuente
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.
fuente
strtok_s
que es básicamentestrtok
con paso explícito de estado.Para cosas simples, solo uso lo siguiente:
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.
fuente
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).
fuente
Muchas sugerencias demasiado complicadas aquí. Pruebe esta sencilla solución std :: string:
fuente
Pensé que para eso era el
>>
operador en secuencias de cadena:fuente
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 incrementarstring
el iterador final de un no está definido . Dicho esto, dadostring str{ "The quick brown fox" }
que ciertamente podemos lograr esto: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:Si no tiene acceso a C ++ 17, deberá sustituirlo
data(str)
como en este ejemplo: http://ideone.com/8kAGoaAunque no se demostró en el ejemplo,
strtok
no es necesario usar el mismo delimitador para cada token. Sin embargo, junto con esta ventaja, hay varios inconvenientes:strtok
no puede ser utilizado en múltiplesstrings
al mismo tiempo: Ya sea unnullptr
ser sometidos a continuar tokenizar la corrientestring
o un nuevochar*
a tokenize debe pasar (hay algunas implementaciones no estándar que hacen apoyar esto, sin embargo, tales como:strtok_s
)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 )strtok
modifica el estadostring
en el que está operando, por lo que no se puede usar enconst string
s,const char*
s o cadenas literales, para simular cualquiera de estos constrtok
o para operar enstring
el contenido de quién necesita ser preservado,str
tendría que copiarse, entonces la copia podría ser operado enc ++ 20nos proporciona
split_view
tokenizar cadenas de manera no destructiva: https://topanswers.xyz/cplusplus?q=749#a874Los métodos anteriores no pueden generar un
vector
in situ tokenizado , es decir, sin abstraerlos en una función auxiliar que no puedan inicializarconst vector<string> tokens
. Esa funcionalidad y la capacidad de aceptar cualquier delimitador de espacios en blanco se pueden aprovechar mediante unistream_iterator
. Por ejemplo dado:const string str{ "The quick \tbrown \nfox" }
podemos hacer esto:Live Example
La construcción requerida de un
istringstream
para esta opción tiene un costo mucho mayor que las 2 opciones anteriores, sin embargo, este costo generalmente se oculta a expensas de lastring
asignació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_iterator
por supuesto, con esta flexibilidad conlleva un mayor gasto, pero nuevamente esto probablemente esté oculto en elstring
costo 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:Live Example
fuente
strtok_s
es el estándar C11, por cierto.strtok_r
es un estándar POSIX2001. Entre ambos, hay una versión reentrante estándarstrtok
para la mayoría de las plataformas.#include <cstring>
solo incluye la versión c99 destrtok
. Entonces, ¿supongo que solo está proporcionando este comentario como material de apoyo, demostrando la disponibilidad específica de implementación destrtok
extensiones?strtok_s
es 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_s
funciones de Microsoft se convirtieron en el estándar C.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ó:
Comente si hay un mejor enfoque para algo en mi código o si algo está mal.
ACTUALIZACIÓN: separador genérico agregado
fuente
Aquí hay un enfoque que le permite controlar si los tokens vacíos están incluidos (como strsep) o excluidos (como strtok).
fuente
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:
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:
Con eso en su lugar, hacer una
getNextToken
función es fácil:Usarlo también es fácil:
Aquí hay un ejemplo en vivo: http://ideone.com/GKtkLQ
fuente
puedes aprovechar boost :: make_find_iterator. Algo similar a esto:
fuente
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.
fuente
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:
Archivo de implementación:
Un escenario de uso sería:
salida:
fuente
boost::tokenizer
es su amigo, pero considere hacer que su código sea portátil con referencia a problemas de internacionalización (i18n) usandowstring
/ enwchar_t
lugar de losstring
/ legacy /char
types.fuente
wchar_t
es un tipo horrible de implementación dependiente que nadie debería usar a menos que sea absolutamente necesario.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.
fuente