example - ¿Cómo tokenize una cadena en C++?
strtok_r example (30)
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 ++?
Aquí hay una clase de tokenizer de muestra que puede 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());
}
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;
}
Esta es una solución simple solo para STL (¡~ 5 líneas!) Usando std::find
y std::find_first_not_of
que maneja las repeticiones del delimitador (como espacios o puntos, por ejemplo), como también los 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 live !
Este es un enfoque que le permite controlar si se incluyen tokens vacíos (como strsep) o se excluyen (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;
}
La clase de Boost.Tokenizer puede hacer este tipo de cosas 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;
}
}
MFC / ATL tiene un tokenizador muy bonito. Desde 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
Me parece extraño que con todos los nerds conscientes de la velocidad aquí en SO, nadie ha presentado una versión que use una tabla de consulta de tiempo de compilación generada para el delimitador (ejemplo de implementación más abajo). El uso de una tabla de consulta y los iteradores deben superar a std :: regex en eficiencia, si no necesita batir regex, solo úselo, es estándar en C ++ 11 y súper flexible.
Algunos ya han sugerido la expresión regular, pero para los noobs aquí hay un ejemplo empaquetado que debería hacer exactamente lo que espera el OP:
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 consulta en tiempo de compilación utilizando 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, 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
Mira este ejemplo. Te podría ayudar ..
#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;
}
Muchas sugerencias demasiado complicadas aquí. Prueba esta solución simple 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;
}
No se ofenda, pero para un problema tan simple, están complicando las cosas. 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 esté haciendo, a menudo me parece mejor reutilizar objetos preexistentes en lugar de crear siempre otros nuevos. (¡Mientras no me olvide de vaciar el vector en medio!)
Referencia: http://www.cplusplus.com/reference/string/string/ .
(Originalmente escribía una respuesta a la pregunta de Doug: Modificación y extracción de cadenas en C ++ basadas en separadores (cerrado) . Pero desde que Martin York cerró esa pregunta con un puntero aquí ... simplemente generalizaré mi código).
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 método split()
simple devuelva un vector<string>
, lo que es realmente útil.
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;
}
Exención de responsabilidad: escribo un software de procesamiento de datos en tiempo real en el que los datos se obtienen 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.
Pensé que para eso era el operador >>
en cadenas de cadenas:
string word; sin >> word;
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 .
#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 t("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 u("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;
}
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);
}
Sé que pediste una solución de C ++, pero podrías 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 un mapeo uno a uno directo al código de su publicación.
Ver más en la documentación de Qt
Si estás dispuesto a usar C, puedes usar la función strtok . Debería prestar atención a los problemas de subprocesos múltiples cuando se usa.
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).
Su caso simple puede construirse fácilmente usando el método std::string::find
. Sin embargo, eche un vistazo a Boost.Tokenizer . Es genial. Boost generalmente tiene algunas herramientas de cuerdas muy interesantes.
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;
}
}
Utilice strtok. En mi opinión, no es necesario crear una clase en torno a la 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 adaptarse a 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 hagas una versión no constante de la cadena. También puede cambiar la lista de delimitadores en medio del análisis.
En mi 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 limpiamente. Es simplemente una solución "basada en C". Es apropiado, es fácil y no tiene que escribir muchos códigos adicionales :-)
Boost tiene una fuerte función de división: boost::algorithm::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"
""
boost::tokenizer
es su amigo, pero considere hacer que su código sea portátil con respecto a los problemas de internacionalización (i18n) utilizando wstring
/ en wchar_t
lugar de los tipos string
/ heredados char
.
#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;
}
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, "-");
El código simple de C ++ (estándar C ++ 98), acepta múltiples delimitadores (especificados en std :: string), 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;
}
La respuesta de Adam Pierce proporciona un tokenizador hilado a mano que toma un const char*
. Es un poco más problemático hacer con los iteradores porque incrementar el iterador final de una string
no está definido . Dicho esto, dada la string str{ "The quick brown fox" }
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 desea abstraer la complejidad mediante el uso de la funcionalidad estándar, como On Freund sugiere que 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á sustituir los data(str)
como en este ejemplo: http://ideone.com/8kAGoa
Aunque no se muestra en el ejemplo, strtok
no necesita usar el mismo delimitador para cada token. Junto con esta ventaja, sin embargo, hay varios inconvenientes:
-
strtok
no se puede usar en variasstrings
al mismo tiempo: se debe pasarnullptr
para continuar con el token de lastring
actual o se debe pasar un nuevochar*
para tokenize (hay algunas implementaciones no estándar que lo admiten, sin embargo, como :strtok_s
) - Por la misma razón, no se puede usar
strtok
en varios subprocesos a la vez (sin embargo, esto puede ser definido por la implementación, por ejemplo: la implementación de Visual Studio es segura para subprocesos ) - Al llamar a
strtok
modifica lastring
que está operando, por lo que no se puede usar enconst string
const char*
literales, para tokenizar ninguna de ellas constrtok
o para operar en unastring
cuyo contenido debe conservarse,str
tendría que ser copiada, entonces la copia podría ser operada en
Los dos métodos anteriores no pueden generar un vector
tokenizado en el lugar, lo que significa que sin abstraerlos en una función auxiliar, no pueden 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: 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
de istringstream
para esta opción tiene un costo mucho mayor que las 2 opciones anteriores, sin embargo, este costo generalmente está oculto en el costo de la asignación de string
.
Si ninguna de las opciones anteriores es lo suficientemente flexible para sus necesidades de tokenización, la opción más flexible es usar un regex_token_iterator
por supuesto, con esta flexibilidad regex_token_iterator
un mayor gasto, pero, de nuevo, esto probablemente esté oculto en el costo de asignación de string
. Digamos, por ejemplo, que es tokenizar en función de comas que no se han escapado, que también comen 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() };
No hay una forma directa de hacer esto. Consulte este código fuente del proyecto de código para descubrir cómo construir una clase para esto.
Si se conoce la longitud máxima de la cadena de entrada a ser tokenizada, se puede explotar esto e implementar una versión muy rápida. A continuación, bosquejo la idea básica, que se inspiró tanto en strtok () como en la estructura de datos de "matriz de sufijos", que describe la segunda edición del capítulo 15 de "Perforación de programación" de Jon Bentley. de uso. La implementación que se muestra se puede extender fácilmente para eliminar los caracteres de espacio en blanco iniciales y finales de los tokens.
Básicamente, se pueden reemplazar los caracteres separadores por caracteres ''/ 0'' que terminan la 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 resultando en tokens vacíos. 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
puedes aprovechar el 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;
}
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
///
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end(), i;
iter sep_b = sep.begin(), sep_e = sep.end();
// search through s
while (b != e){
i = search(b, e, sep_b, sep_e);
// no more separator found
if (i == e){
// it''s not an empty string
if (b != e)
v.push_back(string(b, e));
break;
}
else if (i == b){
// the separator is found and right at the beginning
// in this case, we need to move on and search for the
// next separator
b = i + sep.length();
}
else{
// found the separator
v.push_back(string(b, i));
b = i;
}
}
}
La biblioteca boost es buena, pero no siempre están disponibles. Hacer este tipo de cosas a mano también es un buen ejercicio cerebral. Aquí solo usamos el algoritmo std :: search () de la STL, vea el código anterior.