Java possui um método de divisão conveniente:
String str = "The quick brown fox";
String[] results = str.split(" ");
Existe uma maneira fácil de fazer isso em C ++?
Java possui um método de divisão conveniente:
String str = "The quick brown fox";
String[] results = str.split(" ");
Existe uma maneira fácil de fazer isso em C ++?
Respostas:
Os algoritmos de biblioteca padrão C ++ são universalmente baseados em iteradores, e não em contêineres concretos. Infelizmente, isso dificulta o fornecimento de uma split
função semelhante a Java na biblioteca padrão C ++, mesmo que ninguém defenda que isso seria conveniente. Mas qual seria o seu tipo de retorno? std::vector<std::basic_string<…>>
? Talvez, mas então somos forçados a executar alocações (potencialmente redundantes e caras).
Em vez disso, o C ++ oferece várias maneiras de dividir seqüências de caracteres com base em delimitadores arbitrariamente complexos, mas nenhuma delas é encapsulada tão bem quanto em outros idiomas. As inúmeras maneiras de preencher posts de blog inteiros .
Na sua forma mais simples, você pode iterar usando std::string::find
até pressionar std::string::npos
e extrair o conteúdo usando std::string::substr
.
Uma versão mais fluida (e idiomática, mas básica) para dividir em espaço em branco usaria um std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Usando std::istream_iterator
s , o conteúdo do fluxo de strings também pode ser copiado em um vetor usando seu construtor de intervalo de iterador.
Várias bibliotecas (como o Boost.Tokenizer ) oferecem tokenisers específicos.
A divisão mais avançada requer expressões regulares. O C ++ fornece std::regex_token_iterator
para esse fim em 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{}
);
A classe Boost tokenizer pode tornar esse tipo de coisa bastante simples:
#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;
}
}
Atualizado 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
construtor ( drop_empty_tokens
é o padrão, a alternativa é keep_empty_tokens
).
.h
for C headers)
Aqui está realmente simples:
#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;
}
Use strtok. Na minha opinião, não há necessidade de criar uma classe sobre tokenização, a menos que o strtok não forneça o que você precisa. Pode não ser, mas em mais de 15 anos escrevendo vários códigos de análise em C e C ++, eu sempre usei strtok. Aqui está um exemplo
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Algumas advertências (que podem não atender às suas necessidades). A sequência é "destruída" no processo, o que significa que os caracteres EOS são colocados em linha nos pontos de delimitação. O uso correto pode exigir que você faça uma versão não const da sequência. Você também pode alterar a lista de delimitadores no meio da análise.
Na minha opinião, o código acima é muito mais simples e fácil de usar do que escrever uma classe separada para ele. Para mim, essa é uma daquelas funções que a linguagem fornece e faz bem e de forma limpa. É simplesmente uma solução "baseada em C". É apropriado, fácil e você não precisa escrever muito código extra :-)
Outra maneira rápida é usar getline
. Algo como:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Se desejar, você pode criar um split()
método simples retornando a vector<string>
, o que é realmente útil.
Você pode usar fluxos, iteradores e o algoritmo de cópia para fazer isso diretamente.
#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
assim que eu saiba de onde vem meu objeto, isso é apenas uma questão de estilo.
Nenhum povo ofensa, mas para um problema tão simples, você está fazendo as coisas maneira muito complicado. Existem várias razões para usar o Boost . Mas, para algo tão simples, é como acertar uma mosca com um trenó 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 exemplo (no 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] );
}
E sim, poderíamos ter split () retornado um novo vetor em vez de passar um. É trivial envolver e sobrecarregar. Mas, dependendo do que estou fazendo, geralmente acho melhor reutilizar objetos pré-existentes, em vez de sempre criar novos. (Contanto que eu não esqueça de esvaziar o vetor no meio!)
Referência: http://www.cplusplus.com/reference/string/string/ .
(Eu estava originalmente escrevendo uma resposta à pergunta de Doug: Modificação e extração de seqüências de caracteres C ++ com base em separadores (fechado) . Mas desde que Martin York fechou essa pergunta com um ponteiro aqui ... apenas generalizarei meu código.)
std::string
classe não inclui uma função split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
e o loop while deve ser while (start != string::npos)
. Além disso, verifico a substring para garantir que não esteja vazia antes de inseri-la no vetor.
Uma solução 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;
}
}
O Boost possui uma forte função de divisão: boost :: algoritm :: split .
Programa de exemplo:
#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;
}
Resultado:
"a"
"b"
" c "
""
"e"
"f"
""
Sei que você pediu uma solução C ++, mas você pode considerar isso útil:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
A vantagem sobre o Boost neste exemplo é que ele é direto para um mapeamento para o código da sua postagem.
Veja mais na documentação do Qt
Aqui está uma classe de tokenizer de amostra que pode fazer o que você deseja
//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;
}
Exemplo:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Esta é uma solução simples, apenas para STL (~ 5 linhas!), Que usa std::find
e std::find_first_not_of
que lida com repetições do delimitador (como espaços ou pontos, por exemplo), bem como delimitadores iniciais e finais:
#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);
}
}
Experimente ao vivo !
pystring é uma pequena biblioteca que implementa várias funções de string do Python, incluindo o método split:
#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, "-");
Eu postei esta resposta para uma pergunta semelhante.
Não reinvente a roda. Eu usei várias bibliotecas e a mais rápida e flexível que me deparei é: C ++ String Toolkit Library .
Aqui está um exemplo de como usá-lo que eu postei em outro lugar no 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;
}
Veja este exemplo. Pode ajudar você ..
#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"; }
O MFC / ATL possui um tokenizador muito bom. Do 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
Se você estiver disposto a usar C, poderá usar a função strtok . Você deve prestar atenção aos problemas de multiencadeamento ao usá-lo.
Para coisas simples, eu apenas uso o seguinte:
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;
}
Isenção de responsabilidade covarde: escrevo software de processamento de dados em tempo real em que os dados chegam através de arquivos binários, soquetes ou alguma chamada de API (placas de E / S, câmeras). Eu nunca uso essa função para algo mais complicado ou crítico do que ler arquivos de configuração externos na inicialização.
Você pode simplesmente usar uma biblioteca de expressões regulares e resolvê-lo usando expressões regulares.
Use a expressão (\ w +) e a variável em \ 1 (ou $ 1, dependendo da implementação da biblioteca de expressões regulares).
Muitas sugestões excessivamente complicadas aqui. Experimente esta solução simples 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;
}
Eu pensei que era para isso que o >>
operador nos fluxos de seqüência de caracteres era:
string word; sin >> word;
A resposta de Adam Pierce fornece um tokenizer girado à mão que absorve a const char*
. É um pouco mais problemático com iteradores, porque o incremento do string
iterador final é indefinido . Dito isto, dado string str{ "The quick brown fox" }
que certamente podemos conseguir isso:
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;
}
Se você deseja abstrair a complexidade usando a funcionalidade padrão, como sugere o On Freund, strtok
é uma opção simples:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Se você não tem acesso ao C ++ 17, precisará substituí-lo data(str)
como neste exemplo: http://ideone.com/8kAGoa
Embora não demonstrado no exemplo, strtok
não é necessário usar o mesmo delimitador para cada token. Juntamente com esta vantagem, existem várias desvantagens:
strtok
não pode ser usado em vários strings
ao mesmo tempo: É nullptr
necessário passar um para continuar tokenizando o atual string
ou um novo char*
para tokenizar (há algumas implementações não padrão que suportam isso, no entanto, como:strtok_s
:)strtok
não pode ser usado em vários threads simultaneamente (no entanto, isso pode ser definido como implementação, por exemplo: A implementação do Visual Studio é segura para threads )strtok
modifica a string
operação em que está operando, de modo que não pode ser usada em const string
s, const char*
s ou cadeias literais, para tokenizar qualquer uma delas com strtok
ou para operar com string
conteúdo que precisa ser preservado, str
precisaria ser copiada; a cópia poderia ser operadoc ++ 20nos fornece split_view
tokenizar seqüências de caracteres, de maneira não destrutiva: https://topanswers.xyz/cplusplus?q=749#a874
Os métodos anteriores não podem gerar um tokenizado vector
no local, ou seja, sem abstraí-los para uma função auxiliar que não podem inicializar const vector<string> tokens
. Essa funcionalidade e a capacidade de aceitar qualquer delimitador de espaço em branco podem ser aproveitadas usando um istream_iterator
. Por exemplo: const string str{ "The quick \tbrown \nfox" }
podemos fazer isso:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
A construção necessária de um istringstream
para esta opção tem um custo muito maior do que as 2 opções anteriores, no entanto, esse custo geralmente está oculto nas despesas de string
alocação.
Se nenhuma das opções acima for flexível o suficiente para atender às suas necessidades de tokenização, a opção mais flexível é usar, é regex_token_iterator
claro, essa flexibilidade para aumentar as despesas, mas novamente isso provavelmente está oculto no string
custo de alocação. Digamos, por exemplo, que queremos tokenizar com base em vírgulas sem escape, também consumindo espaço em branco, considerando a seguinte entrada: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
podemos fazer isso:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
é o padrão C11, a propósito. strtok_r
é um padrão POSIX2001. Entre os dois, há uma versão reentrante padrão strtok
para a maioria das plataformas.
#include <cstring>
inclui apenas a versão c99 de strtok
. Portanto, suponho que você esteja apenas fornecendo esse comentário como material de suporte, demonstrando a disponibilidade específica de implementação das strtok
extensões?
strtok_s
é fornecido pelo C11 e como uma extensão autônoma no tempo de execução C da Microsoft. Há um pouco de história aqui, onde as _s
funções da Microsoft se tornaram o padrão C.
Sei que esta pergunta já foi respondida, mas quero contribuir. Talvez minha solução seja um pouco simples, mas é isso que eu criei:
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;
}
Por favor, comente se existe uma abordagem melhor para algo no meu código ou se algo está errado.
UPDATE: adicionado separador genérico
Aqui está uma abordagem que permite controlar se os tokens vazios são incluídos (como strsep) ou excluídos (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;
}
Parece estranho para mim que, com todos nós, nerds conscientes da velocidade aqui no SO, ninguém apresentou uma versão que usa uma tabela de consulta gerada em tempo de compilação para o delimitador (exemplo de implementação mais adiante). Usando uma tabela de consulta e os iteradores devem superar o std :: regex em eficiência, se você não precisar vencer o regex, basta usá-lo, seu padrão a partir do C ++ 11 e super flexível.
Alguns já sugeriram regex, mas para os noobs aqui está um exemplo empacotado que deve fazer exatamente o que o 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;
}
Se precisarmos ser mais rápidos e aceitar a restrição de que todos os caracteres devem ter 8 bits, podemos criar uma tabela de consulta em tempo de compilação usando a metaprogramação:
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>;
Com isso, getNextToken
é fácil criar uma função:
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);
}
Também é fácil usá-lo:
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;
}
Aqui está um exemplo ao vivo: http://ideone.com/GKtkLQ
você pode aproveitar o boost :: make_find_iterator. Algo semelhante a isso:
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;
}
Aqui está meu canivete suíço® de tokenizadores de cordas para dividir cordas por espaço em branco, respondendo por cordas embrulhadas com aspas simples e duplas, além de retirar esses caracteres dos resultados. Usei o RegexBuddy 4.x para gerar a maior parte do snippet de código, mas adicionei um tratamento personalizado para remover citações e algumas outras coisas.
#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;
}
Se o comprimento máximo da string de entrada a ser tokenizada for conhecido, é possível explorar isso e implementar uma versão muito rápida. Estou esboçando a idéia básica abaixo, que foi inspirada pela estrutura de dados strtok () e "array de sufixos", que descreveu "Programming Perls", segunda edição de Jon Bentley, segunda edição, capítulo 15. A classe C ++, neste caso, apenas fornece alguma organização e conveniência de uso. A implementação mostrada pode ser facilmente estendida para remover caracteres de espaço em branco à esquerda e à direita nos tokens.
Basicamente, pode-se substituir os caracteres separadores por caracteres '\ 0' que terminam com string e definir ponteiros para os tokens dentro da string modificada. No caso extremo, quando a string consiste apenas em separadores, obtém-se o comprimento da string mais 1 tokens vazios resultantes. É prático duplicar a sequência a ser modificada.
Arquivo de cabeçalho:
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;
}
};
Arquivo de implementação:
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++ ] );
}
Um cenário de uso seria:
// 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 ) );
}
resultado:
Item1
Item2
Item3
boost::tokenizer
é seu amigo, mas considere tornar seu código portátil com referência a problemas de internacionalização (i18n) usando wstring
/ em wchar_t
vez dos tipos string
/ legados 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;
}
wchar_t
é um tipo dependente de implementação horrível que ninguém deve usar, a menos que seja absolutamente necessário.
O código C ++ simples (C ++ 98 padrão), aceita vários delimitadores (especificados em std :: string), usa apenas vetores, strings 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;
}