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 ++?
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 ++?
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 golpee std::string::npos
y 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_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:
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{}
);
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;
}
}
char_separator
constructor ( drop_empty_tokens
es el predeterminado, la alternativa es keep_empty_tokens
).
.h
para los encabezados C)
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;
}
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 :-)
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.
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);
}
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 #.
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).
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 ser while (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:
#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;
}
}
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"
""
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
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());
}
Esta es una solución simple de solo STL (¡~ 5 líneas!) Que utiliza std::find
y std::find_first_not_of
que 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 !
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, "-");
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;
}
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;
}
while ( is >> tmps ) { std::cout << tmps << "\n"; }
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
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.
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.
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).
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;
}
Pensé que para eso era el >>
operador en secuencias de cadena:
string word; sin >> word;
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 string
el 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;
}
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, 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últiples strings
al mismo tiempo: Ya sea un nullptr
ser sometidos a continuar tokenizar la corriente string
o un nuevo char*
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 estado string
en el que está operando, por lo que no se puede usar en const string
s, const char*
s o cadenas literales, para simular cualquiera de estos con strtok
o para operar en string
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#a874
Los métodos anteriores no pueden generar un vector
in 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>() };
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 la string
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 el string
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:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
es el estándar C11, por cierto. strtok_r
es un estándar POSIX2001. Entre ambos, hay una versión reentrante estándar strtok
para la mayoría de las plataformas.
#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 strtok
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ó:
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
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;
}
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 getNextToken
funció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
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;
}
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;
}
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
boost::tokenizer
es su amigo, pero considere hacer que su código sea portátil con referencia a problemas de internacionalización (i18n) usando wstring
/ en wchar_t
lugar de los string
/ legacy / char
types.
#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;
}
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.
#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;
}