Como tokenizar uma string em C ++?

414

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 ++?

Bill the Lizard
fonte
172
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 ..
Paschalis
1
Possível duplicata de Dividir uma string em C ++?
KOB
2
Por que tudo em C ++ precisa ser uma luta?
23819 Wael Assaf

Respostas:

145

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:

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{}
);
Konrad Rudolph
fonte
53
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>

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;
    }
}
Ferruccio
fonte
1
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>
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;
}
Adam Pierce
fonte
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 :-)

Marca
fonte
42
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?
precisa saber é o seguinte
105

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.

user35978
fonte
2
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);
}
KeithB
fonte
17
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) << "\"" << 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.)

Mr.Ree
fonte
12
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>

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;
    }
}
wb
fonte
5
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.
Marek R
35

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"
""
Raz
fonte
26

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

sivabudh
fonte
22

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());
}
vzczc
fonte
19

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);
    }
}

Experimente ao vivo !

Parham
fonte
3
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, "-");
dbr
fonte
3
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>

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;
}
DannyK
fonte
8

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;
}
sohesado
fonte
1
Eu fariawhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix
6

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
Jim In Texas
fonte
1
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.

Em Freund
fonte
3
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.
Matthias
4

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.

jilles de wit
fonte
4

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).

Fawix
fonte
+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:

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;
}
David919
fonte
4

Eu pensei que era para isso que o >>operador nos fluxos de seqüência de caracteres era:

string word; sin >> word;
Daren Thomas
fonte
1
Minha culpa foi por dar um exemplo ruim (simples demais). Até onde eu sei, isso só funciona quando seu delimitador é um espaço em branco.
Bill the Lizard
4

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 stringiterador 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;
}

Live Example


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:

  1. 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 :)
  2. Pelo mesmo motivo, strtoknã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 )
  3. 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

nos fornece split_viewtokenizar 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 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:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Live Example

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:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Live Example

Jonathan Mee
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ã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:

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

NutCracker
fonte
Usou sua solução da multidão :) Posso modificar seu código para adicionar qualquer separador?
Zac
1
@Zac feliz que você gostou e ofc você pode modificá-lo ... apenas adicionar uma seção de atualização em negrito para a minha resposta ...
Nutcracker
2

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;
}
Darren Smith
fonte
2

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

odinthenerd
fonte
1
É 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>
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;
}
Arash
fonte
1

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;
}
kayleeFrye_onDeck
fonte
1
(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:

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
Angel Sinigersky
fonte
0

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>

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;
}
jochenleidner
fonte
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 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;
}
vsoftco
fonte