Eu não posso acreditar que esta tarefa de rotina é como uma dor de cabeça em c ++
wfbarksdale
6
Não é uma dor de cabeça em c ++ - existem várias maneiras de alcançá-lo. programadores são menos conscientes de c ++ de c # - seu marketing sobre e investimentos ... ver isso por vários c ++ opções para alcançar o mesmo: cplusplus.com/faq/sequences/strings/split
hB0
9
@ hB0 passar por muitas perguntas, respostas e ainda não decidir meios é uma dor de cabeça. os que se precisa que a biblioteca, o outro é apenas para os espaços, o outro não trata espaços ..
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 splitfunçã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::findaté pressionar std::string::npose 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_iterators , 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_iteratorpara esse fim em 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{});
Infelizmente, o aumento nem sempre está disponível para todos os projetos. Vou ter que procurar uma resposta sem impulso.
FuzzyBunnySlippers
36
Nem todo projeto é aberto ao "código aberto". Eu trabalho em indústrias fortemente regulamentadas. Não é realmente um problema. É apenas um fato da vida. O impulso não está disponível em qualquer lugar.
FuzzyBunnySlippers
5
@NonlinearIdeas A outra pergunta / resposta não era sobre projetos de código aberto. O mesmo vale para qualquer projeto. Dito isso, é claro que eu entendo sobre padrões restritos, como o MISRA C, mas entendemos que você constrói tudo do zero de qualquer maneira (a menos que encontre uma biblioteca compatível - uma raridade). Enfim, dificilmente o argumento é que “o Boost não está disponível” - é que você tem requisitos especiais para os quais quase qualquer resposta de propósito geral seria inadequada.
Konrad Rudolph
1
@NonlinearIdeas Nesse caso, as outras respostas que não são do tipo Boost também não são compatíveis com MISRA.
Konrad Rudolph
3
@Dmitry O que é "STL barf" ?! E toda a comunidade é muito a favor da substituição do pré-processador C - na verdade, existem propostas para fazer isso. Mas sua sugestão de usar PHP ou alguma outra linguagem seria um grande passo para trás.
21916 Konrad Rudolph
188
A classe Boost tokenizer pode tornar esse tipo de coisa bastante simples:
#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;}}
Atualizado 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;}}
Coisas boas, eu usei isso recentemente. Meu compilador do Visual Studio tem um som estranho até eu usar um espaço em branco para separar os dois caracteres ">" antes dos bits dos tokens (texto, sep): (erro C2947: esperando que '>' encerre a lista de argumentos de modelo encontrada '> > ')
AndyUK
@AndyUK sim, sem o espaço que o compilador o analisa como um operador de extração, em vez de dois modelos de fechamento.
EnabrenTane
Teoricamente que tem sido corrigido em C ++ 0x
David Souther
3
cuidado com os terceiros parâmetros do char_separatorconstrutor ( drop_empty_tokensé o padrão, a alternativa é keep_empty_tokens).
Benoit
5
@uk - É um sufixo comumente usado para arquivos de cabeçalho C ++. (like .hfor C headers)
Ferruccio 12/12
167
Aqui está realmente simples:
#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;}
preciso adicionar um protótipo para esse método no arquivo .h?
Suhrob Samiev
5
Esta não é exatamente a melhor "resposta", pois ainda usa uma string literal que é a matriz de caracteres constantes C simples. Eu acredito que o questionador estava perguntando se ele poderia tokenizar uma string C ++ que é do tipo "string" introduzida por esta.
Vijay Kumar Kanta
Isso precisa de uma nova resposta, porque eu suspeito fortemente que a inclusão de expressões regulares no C ++ 11 tenha mudado qual seria a melhor resposta.
Onívoro
113
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 :-)
Não que eu não goste de C, no entanto, strtok não é seguro para threads e você precisa ter certeza de que a string que você envia contém um caractere nulo para evitar um possível estouro de buffer.
tloach
11
Existe strtok_r, mas essa foi uma pergunta em C ++.
O contrato do Prof. Falken violou
3
@tloach: MS em C ++ strtok compilador é seguro segmento como a variável estática interno é criado no TLS (rosca de armazenamento local) (na verdade, é compilador dependia)
Ahmed Said
3
@ahmed: thread safe significa mais do que apenas poder executar a função duas vezes em threads diferentes. Nesse caso, se o encadeamento for modificado enquanto o strtok estiver em execução, é possível que a string seja válida durante toda a execução do strtok, mas o strtok continuará bagunçado porque a string foi alterada, agora já passou do caractere nulo e continue lendo a memória até obter uma violação de segurança ou encontrar um caractere nulo. Este é um problema com as funções originais da string C, se você não especificar um comprimento em algum lugar, poderá encontrar problemas.
tloach
4
strtok requer um ponteiro para uma matriz de caracteres terminada em nulo não const, que não é uma criatura comum para se encontrar no código c ++ ... qual é a sua maneira favorita de converter isso a partir de um std :: string?
Eu tive problemas ao usar essa técnica com caracteres 0x0A na string que fazia o loop while sair prematuramente. Caso contrário, é uma boa solução simples e rápida.
Ryan H.
4
Isso é bom, mas é preciso ter em mente que, ao fazer isso, o delimitador padrão '\ n' não é considerado. Este exemplo vai funcionar, mas se você estiver usando algo como: while (getline (INFILE, palavra,' ')), onde INFILE é objeto ifstream contendo várias linhas você vai obter resultados funnny ..
hackrock
é muito ruim getline retorna a corrente em vez da corda, tornando-o inutilizável em listas de inicialização sem armazenamento temporário
fuzzyTew
1
Legal! Sem impulso e C ++ 11, boa solução para os projetos herdados por aí!
Deqing
1
Essa é a resposta, o nome da função é um pouco estranho.
Nils
82
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);}
Acho esses std :: irritantes para ler .. por que não usar "using"?
user35978
80
@Vadi: porque editar a publicação de outra pessoa é bastante intrusivo. @ pheze: Eu prefiro deixar stdassim que eu saiba de onde vem meu objeto, isso é apenas uma questão de estilo.
Matthieu M.
7
Entendo sua razão e acho que é realmente uma boa escolha se funcionar para você, mas, do ponto de vista pedagógico, concordo com o pheze. É mais fácil ler e entender um exemplo completamente estranho como este, com um "using namespace std" na parte superior porque requer menos esforço para interpretar as seguintes linhas ... especialmente neste caso, porque tudo é da biblioteca padrão. Você pode facilitar a leitura e a localização óbvia de onde os objetos são originados por uma série de "using std :: string;" etc. Especialmente porque a função é tão curta.
Cheshirekow
61
Apesar de os prefixos "std ::" serem irritantes ou feios, é melhor incluí-los no código de exemplo para ficar completamente claro de onde essas funções vêm. Se eles o incomodarem, é trivial substituí-los por um "uso" depois de roubar o exemplo e reivindicá-lo como seu.
amigos estão dizendo sobre dlchambers
20
Sim! o que ele disse! práticas recomendadas é usar o prefixo std. Qualquer grande base de código sem dúvida terá suas próprias bibliotecas e espaços para nome e usar "using namespace std" causará dores de cabeça quando você começar a causar conflitos no espaço para nome.
Miek
48
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) << "\"" << endlint
main(){vector<string> v;
split( v,"A:PEP:909:Inventory Item",":");for(unsignedint 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!)
Por que definir uma macro que você usa apenas em um só lugar. E como o seu UASSERT é melhor do que a afirmação padrão. Dividir a comparação em três tokens assim não exige nada além de vírgulas do que você precisaria.
Crelbor 13/05
1
Talvez a macro UASSERT mostre (na mensagem de erro) o relacionamento real entre (e valores) dos dois valores comparados? Na verdade, é uma boa ideia, IMHO.
GhassanPL
10
Ugh, por que a std::stringclasse não inclui uma função split ()?
Shickadance
Eu acho que a última linha no loop while deve ser 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.
31712 John K
@JohnK Se a entrada tiver dois delimitadores consecutivos, então claramente a string entre eles está vazia e deve ser inserida no vetor. Se valores vazios não são aceitáveis para uma finalidade específica, isso é outra coisa, mas IMHO tais restrições devem ser impostas fora desse tipo de função de propósito muito geral.
precisa
46
Uma solução 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;}}
Essa deve ser a resposta mais bem classificada. Esta é a maneira certa de fazer isso em C ++> = 11.
omniforme
1
Fico feliz por ter percorrido todo o caminho até esta resposta (atualmente só tenho 9 votos positivos). É exatamente assim que um código C ++ 11 deve ser para esta tarefa!
YePhIcK
Excelente resposta que não depende de bibliotecas externas e usos bibliotecas já disponíveis
Andrew
1
Ótima resposta, oferecendo o máximo de flexibilidade em delimitadores. Algumas advertências: o uso de \ s + regex evita tokens vazios no meio do texto, mas fornece um primeiro token vazio se o texto começar com espaço em branco. Além disso, o regex parece lento: no meu laptop, para 20 MB de texto aleatório, são necessários 0,6 s, comparado a 0,014 s para strtok, strsep ou a resposta de Parham usando str.find_first_of ou 0,027 s para Perl ou 0,021 s para Python . Para textos curtos, a velocidade pode não ser uma preocupação.
Mark Gates,
2
Ok, talvez pareça legal, mas isso é claramente o uso excessivo de expressões regulares. Razoável apenas se você não se importa com o desempenho.
#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 é uma solução simples, apenas para STL (~ 5 linhas!), Que usa std::finde std::find_first_not_ofque 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);}}
Essa é boa, mas acho que você precisa usar find_first_of () em vez de find () para que isso funcione corretamente com vários delimitadores.
2
@ user755921 vários delimitadores são ignorados ao localizar a posição inicial com find_first_not_of.
Iniciante
16
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,"-");
Uau, você respondeu minha pergunta imediata e muitas perguntas futuras. Eu entendo que c ++ é poderoso. Mas ao dividir uma string resulta em código-fonte como as respostas acima, é claramente desanimador. Gostaria muito de saber de outras bibliotecas como essa que reduzem as conveniências de idiomas de nível superior.
Ross
uau, você seriamente acabou de fazer o meu dia !! não sabia sobre pystring. isso vai me poupar muito tempo!
accraze
11
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>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;}
Essa função Tokenize () ignorará os tokens vazios; por exemplo, se houver substring "%%" na string principal, não haverá retorno do token vazio. É pulado.
Sheen
4
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.
Note que strtok modifica a string que você está verificando, então você não pode usá-la em const char * strings sem fazer uma cópia.
Graeme Perrow
9
O problema da multithreading é que o strtok usa uma variável global para acompanhar onde está; portanto, se você tem dois threads que usam strtok, obterá um comportamento indefinido.
JohnMcG
@JohnMcG Ou apenas use strtok_sbasicamente strtokcom passagem explícita de estado.
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.
+1 por sugerir regex, se você não precisar de velocidade de dobra, é a solução mais flexível, ainda não suportada em todos os lugares, mas com o passar do tempo isso se tornará menos importante.
28514 od odenerenerd
+1 de mim, tentei <regex> no c ++ 11. Tão simples e elegante
StahlRat
4
Muitas sugestões excessivamente complicadas aqui. Experimente esta solução simples std :: string:
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, strtoknão é necessário usar o mesmo delimitador para cada token. Juntamente com esta vantagem, existem várias desvantagens:
strtoknão pode ser usado em vários stringsao mesmo tempo: É nullptrnecessário passar um para continuar tokenizando o atual stringou um novo char*para tokenizar (há algumas implementações não padrão que suportam isso, no entanto, como:strtok_s :)
A chamada strtokmodifica a stringoperação em que está operando, de modo que não pode ser usada em const strings, const char*s ou cadeias literais, para tokenizar qualquer uma delas com strtokou para operar com stringconteúdo que precisa ser preservado, strprecisaria ser copiada; a cópia poderia ser operado
Os métodos anteriores não podem gerar um tokenizado vectorno 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:
A construção necessária de um istringstreampara 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 stringalocaçã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_iteratorclaro, essa flexibilidade para aumentar as despesas, mas novamente isso provavelmente está oculto no stringcusto 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:
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 strtokpara a maioria das plataformas.
Andon M. Coleman
@ AndonM.Coleman Mas esta é uma questão de c ++ , e em C ++ #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 strtokextensões?
Jonathan Mee
1
Simplesmente não é tão fora do padrão quanto as pessoas possam acreditar. 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 _sfunções da Microsoft se tornaram o padrão C.
Andon M. Coleman
@ AndonM.Coleman Certo, estou com você. Obviamente, se estiver no padrão C11, a interface e a implementação têm restrições impostas a eles que exigem comportamento idêntico, independentemente da plataforma. Agora, o único problema é garantir que a função C11 esteja disponível para nós em todas as plataformas. Esperemos que o padrão C11 seja algo que C ++ 17 ou C ++ 20 opte por pegar.
Jonathan Mee
3
Sei que esta pergunta já foi respondida, mas quero contribuir. Talvez minha solução seja um pouco simples, mas é isso que eu criei:
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 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;}
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...>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>;
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 endauto second = std::find_if(begin,end,Table{});//find first delim or endreturn 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;}return0;}
É possível tokenizar com um delimitador de String?
Galigator
essa versão é otimizada apenas para delimitadores de um caractere, o uso de uma tabela de consulta não é adequada para delimitadores de vários caracteres (string), portanto é mais difícil superar a regex em eficiência.
odinthenerd
1
você pode aproveitar o boost :: make_find_iterator. Algo semelhante a isso:
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;}
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.
(Para baixo) de votos pode ser tão construtivo como upvotes, mas não quando você não deixar um comentário a respeito de porque ...
kayleeFrye_onDeck
1
Eu empatei você, mas pode ser porque o código parece bastante assustador para o programador pesquisando 'como dividir uma string' especialmente sem documentação
mattshu 12/01
Obrigado @mattshu! São os segmentos de regex que o tornam assustador ou algo mais?
kayleeFrye_onDeck 16/01
0
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:
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::tokenizeré seu amigo, mas considere tornar seu código portátil com referência a problemas de internacionalização (i18n) usando wstring/ em wchar_tvez dos tipos string/ legados char.
#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;}
Definitivamente, "legado" não está correto e wchar_té um tipo dependente de implementação horrível que ninguém deve usar, a menos que seja absolutamente necessário.
CoffeeandCode
O uso do wchar_t de alguma forma não resolve automaticamente nenhum problema do i18n. Você usa codificações para resolver esse problema. Se você estiver dividindo uma string por um delimitador, está implícito que o delimitador não colide com o conteúdo codificado de nenhum token dentro da string. Pode ser necessário escapar, etc. wchar_t não é uma solução mágica para isso.
yonil 7/09/15
0
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 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;}
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é pressionarstd::string::npos
e extrair o conteúdo usandostd::string::substr
.Uma versão mais fluida (e idiomática, mas básica) para dividir em espaço em branco usaria um
std::istringstream
: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:fonte
A classe Boost tokenizer pode tornar esse tipo de coisa bastante simples:
Atualizado para C ++ 11:
fonte
char_separator
construtor (drop_empty_tokens
é o padrão, a alternativa ékeep_empty_tokens
)..h
for C headers)Aqui está realmente simples:
fonte
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
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 :-)
fonte
Outra maneira rápida é usar
getline
. Algo como:Se desejar, você pode criar um
split()
método simples retornando avector<string>
, o que é realmente útil.fonte
Você pode usar fluxos, iteradores e o algoritmo de cópia para fazer isso diretamente.
fonte
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 #.
Por exemplo (no caso de Doug),
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.)
fonte
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 serwhile (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:fonte
O Boost possui uma forte função de divisão: boost :: algoritm :: split .
Programa de exemplo:
Resultado:
fonte
Sei que você pediu uma solução C ++, mas você pode considerar isso útil:
Qt
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
fonte
Aqui está uma classe de tokenizer de amostra que pode fazer o que você deseja
Exemplo:
fonte
Esta é uma solução simples, apenas para STL (~ 5 linhas!), Que usa
std::find
estd::find_first_not_of
que lida com repetições do delimitador (como espaços ou pontos, por exemplo), bem como delimitadores iniciais e finais:Experimente ao vivo !
fonte
pystring é uma pequena biblioteca que implementa várias funções de string do Python, incluindo o método split:
fonte
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.
fonte
Veja este exemplo. Pode ajudar você ..
fonte
while ( is >> tmps ) { std::cout << tmps << "\n"; }
O MFC / ATL possui um tokenizador muito bom. Do MSDN:
fonte
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.
fonte
strtok_s
basicamentestrtok
com passagem explícita de estado.Para coisas simples, eu apenas uso o seguinte:
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.
fonte
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).
fonte
Muitas sugestões excessivamente complicadas aqui. Experimente esta solução simples std :: string:
fonte
Eu pensei que era para isso que o
>>
operador nos fluxos de seqüência de caracteres era:fonte
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 dostring
iterador final é indefinido . Dito isto, dadostring str{ "The quick brown fox" }
que certamente podemos conseguir isso:Live Example
Se você deseja abstrair a complexidade usando a funcionalidade padrão, como sugere o On Freund,
strtok
é uma opção simples:Se você não tem acesso ao C ++ 17, precisará substituí-lo
data(str)
como neste exemplo: http://ideone.com/8kAGoaEmbora 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áriosstrings
ao mesmo tempo: Énullptr
necessário passar um para continuar tokenizando o atualstring
ou um novochar*
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 astring
operação em que está operando, de modo que não pode ser usada emconst string
s,const char*
s ou cadeias literais, para tokenizar qualquer uma delas comstrtok
ou para operar comstring
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#a874Os 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 inicializarconst vector<string> tokens
. Essa funcionalidade e a capacidade de aceitar qualquer delimitador de espaço em branco podem ser aproveitadas usando umistream_iterator
. Por exemplo:const string str{ "The quick \tbrown \nfox" }
podemos fazer isso:Live Example
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 destring
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 nostring
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:Live Example
fonte
strtok_s
é o padrão C11, a propósito.strtok_r
é um padrão POSIX2001. Entre os dois, há uma versão reentrante padrãostrtok
para a maioria das plataformas.#include <cstring>
inclui apenas a versão c99 destrtok
. Portanto, suponho que você esteja apenas fornecendo esse comentário como material de suporte, demonstrando a disponibilidade específica de implementação dasstrtok
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:
Por favor, comente se existe uma abordagem melhor para algo no meu código ou se algo está errado.
UPDATE: adicionado separador genérico
fonte
Aqui está uma abordagem que permite controlar se os tokens vazios são incluídos (como strsep) ou excluídos (como strtok).
fonte
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:
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:
Com isso,
getNextToken
é fácil criar uma função:Também é fácil usá-lo:
Aqui está um exemplo ao vivo: http://ideone.com/GKtkLQ
fonte
você pode aproveitar o boost :: make_find_iterator. Algo semelhante a isso:
fonte
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.
fonte
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:
Arquivo de implementação:
Um cenário de uso seria:
resultado:
fonte
boost::tokenizer
é seu amigo, mas considere tornar seu código portátil com referência a problemas de internacionalização (i18n) usandowstring
/ emwchar_t
vez dos tiposstring
/ legadoschar
.fonte
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.
fonte