Como itero as palavras de uma string?

2986

Estou tentando repetir as palavras de uma string.

Pode-se supor que a sequência seja composta por palavras separadas por espaços em branco.

Observe que não estou interessado em funções de string C ou nesse tipo de manipulação / acesso a caracteres. Além disso, priorize a elegância em detrimento da eficiência em sua resposta.

A melhor solução que tenho agora é:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

Existe uma maneira mais elegante de fazer isso?

Ashwin Nanjappa
fonte
617
Cara ... Elegância é apenas uma maneira elegante de dizer "eficiência que parece bonita" no meu livro. Não se coíbe de usar funções C e métodos rápidos para realizar qualquer coisa só porque não está contido dentro de um modelo;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon
21
@Eduardo: isso é errado também ... você precisa iss teste entre tentando transmitir um outro valor e usar esse valor, ou sejastring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Várias opções em C ++ para fazer isso por padrão: cplusplus.com/faq/sequences/strings/split
hB0
14
Há mais na elegância do que apenas eficiência. Atributos elegantes incluem baixa contagem de linhas e alta legibilidade. O IMHO Elegance não é um proxy para a eficiência, mas para a manutenção.
Matt

Respostas:

1369

Para o que vale, aqui está outra maneira de extrair tokens de uma sequência de entrada, contando apenas com os recursos padrão da biblioteca. É um exemplo do poder e da elegância por trás do design do STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

Em vez de copiar os tokens extraídos para um fluxo de saída, pode-se inseri-los em um contêiner, usando o mesmo copyalgoritmo genérico .

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... ou crie o vectordiretamente:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
fonte
164
É possível especificar um delimitador para isso? Como, por exemplo, dividir vírgulas?
L3dx 06/08/2009
15
@ Jonathan: \ n não é o delimitador, neste caso, é o deliminador da saída para cout.
huy
772
Essa é uma solução ruim, pois não requer nenhum outro delimitador, portanto não é escalável e não pode ser mantido.
HelloWorld 10/01
37
Na verdade, isso pode funcionar bem com outros delimitadores (embora fazer alguns seja um pouco feio). Crie uma faceta ctype que classifique os delimitadores desejados como espaço em branco, crie um código de idioma que contenha esse aspecto e imbua o stringstream com esse código de idioma antes de extrair cadeias.
Jerry Coffin
53
@Kinderchocolate "A string pode ser assumida como composta por palavras separadas por espaços em branco" - Hmm, não parece uma solução ruim para o problema da pergunta. "não escalável e não manejável" - Hah, legal.
Christian Rau
2426

Eu uso isso para dividir a string por um delimitador. O primeiro coloca os resultados em um vetor pré-construído, o segundo retorna um novo vetor.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Observe que esta solução não ignora tokens vazios; portanto, a seguir, você encontrará 4 itens, um dos quais está vazio:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran
fonte
86
Para evitar que ele empty()if (!item.empty()) elems.push_back(item)
salte
11
Que tal o delim contém dois caracteres como ->?
Herohuyongtao
7
@herohuyongtao, esta solução funciona apenas para delimitadores de caracteres únicos.
precisa
4
@JeshwanthKumarNK, não é necessário, mas permite que você faça coisas como passar o resultado diretamente para uma função como esta: f(split(s, d, v))enquanto ainda tem o benefício de uma pré-alocação, vectorse quiser.
precisa saber é o seguinte
8
Advertência: split ("one: two :: three", ':') e split ("one: two :: three:", ':') retornam o mesmo valor.
dshin 9/09/15
834

Uma possível solução usando o Boost pode ser:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Essa abordagem pode ser ainda mais rápida que a stringstreamabordagem. E como essa é uma função de modelo genérica, ela pode ser usada para dividir outros tipos de strings (wchar, etc. ou UTF-8) usando todos os tipos de delimitadores.

Veja a documentação para detalhes.

ididak
fonte
35
A velocidade é irrelevante aqui, pois ambos os casos são muito mais lentos que uma função semelhante a strtok.
Tom
45
E para aqueles que ainda não tem impulso ... cópias bcp mais de 1.000 arquivos para este :)
Roman Starkov
12
Aviso, quando recebe uma string vazia (""), esse método retorna um vetor contendo a string "". Portanto, adicione um "if (! String_to_split.empty ())" antes da divisão.
Offirmo
29
Os desenvolvedores do @Ian Embedded não estão todos usando o boost.
ACK_stoverflow
31
como adendo: eu uso o boost somente quando preciso; normalmente, prefiro adicionar à minha própria biblioteca de códigos que é autônoma e portátil para que eu possa obter um código específico pequeno e preciso, que atinja um determinado objetivo. Dessa forma, o código não é público, de bom desempenho, trivial e portátil. O Boost tem o seu lugar, mas eu sugeriria que é um pouco exagerado para seqüências de tokens: você não transportaria toda a sua casa para uma empresa de engenharia para que um novo prego fosse martelado na parede para pendurar uma foto ... eles podem fazer isso extremamente bem, mas os prosare superam em muito os contras.
GMasucci
362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}
kev
fonte
12
Você também pode dividir em outros delimitadores se usar getlinena whilecondição, por exemplo, para dividir por vírgulas, use while(getline(ss, buff, ',')).
Ali
181

Para aqueles com quem não é bom sacrificar toda a eficiência pelo tamanho do código e ver "eficiente" como um tipo de elegância, o seguinte deve ser um ponto ideal (e acho que a classe de contêiner de modelos é uma adição incrivelmente elegante.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Normalmente, eu escolho usar std::vector<std::string>tipos como meu segundo parâmetro ( ContainerT) ... mas list<>é muito mais rápido do que vector<>quando o acesso direto não é necessário, e você pode até criar sua própria classe de string e usar algo como std::list<subString>onde subStringnão faz cópias para uma velocidade incrível aumenta.

É mais que o dobro da velocidade do tokenize mais rápido nesta página e quase 5 vezes mais rápido do que algumas outras. Além disso, com os tipos de parâmetros perfeitos, você pode eliminar todas as cópias de sequência e lista para aumentar a velocidade.

Além disso, ele não realiza o retorno (extremamente ineficiente) do resultado, mas passa os tokens como referência, permitindo também que você construa tokens usando várias chamadas, se assim o desejar.

Por fim, permite especificar se é necessário aparar tokens vazios dos resultados por meio de um último parâmetro opcional.

Tudo o que precisa é std::string... o resto é opcional. Ele não usa fluxos ou a biblioteca de reforço, mas é flexível o suficiente para poder aceitar alguns desses tipos externos naturalmente.

Marius
fonte
5
Sou bastante fã disso, mas para o g ++ (e provavelmente uma boa prática) qualquer pessoa que use isso desejará typedefs e typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Em seguida, substitua o value_type e size_types de acordo.
28411 aws
11
Para aqueles de nós para quem o material do modelo e o primeiro comentário são completamente estranhos, um exemplo de uso com as inclusões necessárias seria adorável.
Wes Miller
3
Ahh bem, eu descobri. Coloquei as linhas C ++ do comentário do aws dentro do corpo da função tokenize (), depois editei as linhas tokens.push_back () para alterar o ContainerT :: value_type para apenas ValueType e alterei (ContainerT :: value_type :: size_type) para ( Tamanho Tipo). Corrigidos os bits que o g ++ estava reclamando. Apenas invoque-o como tokenize (some_string, some_vector);
21312 Wes Wes Miller
2
Além de executar alguns testes de desempenho em dados de amostra, reduzi-o principalmente ao menor número possível de instruções e também ao mínimo possível de cópias de memória ativadas pelo uso de uma classe de substring que apenas referencia desvios / comprimentos em outras seqüências de caracteres. (Eu rolei o meu próprio, mas existem algumas outras implementações). Infelizmente, não há muito mais que se possa fazer para melhorar isso, mas foram possíveis aumentos incrementais.
Marius
3
Essa é a saída correta para quando trimEmpty = true. Lembre-se de que "abo"não é um delimitador nesta resposta, mas a lista de caracteres do delimitador. Seria simples para modificá-lo a tomar uma única corda delimitador de caracteres (eu acho que str.find_first_ofdeve mudar para str.find_first, mas eu poderia ser errado ... não pode testar)
Marius
158

Aqui está outra solução. É compacto e razoavelmente eficiente:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Pode ser facilmente modelado para lidar com separadores de cordas, cordas largas, etc.

Observe que a divisão ""resulta em uma única cadeia vazia e a divisão ","(por exemplo, sep) resulta em duas cadeias vazias.

Também pode ser facilmente expandido para pular tokens vazios:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Se você deseja dividir uma sequência em vários delimitadores enquanto ignora tokens vazios, esta versão pode ser usada:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}
Alec Thomas
fonte
10
A primeira versão é simples e faz o trabalho perfeitamente. A única alteração que eu faria seria retornar o resultado diretamente, em vez de passá-lo como um parâmetro.
gregschlom
2
A saída é passada como um parâmetro para eficiência. Se o resultado fosse retornado, seria necessária uma cópia do vetor ou uma alocação de heap que precisaria ser liberada.
Alec Thomas
2
Um pequeno adendo ao meu comentário acima: essa função pode retornar o vetor sem penalidade se estiver usando a semântica de movimento do C ++ 11.
precisa
7
@AlecThomas: Mesmo antes do C ++ 11, a maioria dos compiladores não otimizava a cópia de retorno via NRVO? (+1 de qualquer maneira, muito sucinta)
Marcelo Cantos
11
De todas as respostas, essa parece ser uma das mais atraentes e flexíveis. Juntamente com o getline com um delimitador, embora seja uma solução menos óbvia. O padrão c ++ 11 não tem nada para isso? Atualmente, o c ++ 11 suporta cartões perfurados?
Spacen Jasset 11/08/2015
123

Esta é a minha maneira favorita de percorrer uma string. Você pode fazer o que quiser por palavra.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
gnomed
fonte
É possível declarar wordcomo um char?
abatishchev
Desculpe abatishchev, C ++ não é meu ponto forte. Mas imagino que não seria difícil adicionar um loop interno para percorrer todos os caracteres de cada palavra. Mas, no momento, acredito que o loop atual depende de espaços para separação de palavras. A menos que você saiba que existe apenas um único caractere entre todos os espaços, nesse caso, você pode simplesmente converter "word" em um caractere ... desculpe, eu não posso ajudar mais, eu tenho sentido melhorar o meu C ++
gnomed
11
se você declarar a palavra como um caractere, ela repetirá todos os caracteres que não sejam espaços em branco. É simples o suficiente para tentar:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Isso é semelhante à pergunta do estouro de pilha Como faço para tokenizar uma string em C ++? .

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}
Ferruccio
fonte
Isso materializa uma cópia de todos os tokens ou mantém apenas a posição inicial e final do token atual?
einpoklum
66

Eu gosto do seguinte porque coloca os resultados em um vetor, suporta uma string como delim e dá controle sobre a manutenção de valores vazios. Mas, não parece tão bom então.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Obviamente, o Boost tem um split()que funciona parcialmente assim. E, se por 'espaço em branco', você realmente quer dizer qualquer tipo de espaço em branco, usar a divisão do Boost com is_any_of()ótimas obras.

Shadow2531
fonte
Finalmente uma solução que está a lidar com fichas vazios correctamente em ambos os lados da cadeia
fmuecke
53

O STL ainda não possui esse método disponível.

No entanto, você pode usar a strtok()função C usando o std::string::c_str()membro ou pode escrever sua própria. Aqui está um exemplo de código que encontrei após uma pesquisa rápida no Google ( "divisão de cadeia de caracteres STL" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Retirado de: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Se você tiver dúvidas sobre o exemplo de código, deixe um comentário e eu explicarei.

E só porque ele não implementa um typedefiterador chamado ou sobrecarrega o <<operador não significa que seja um código incorreto. Eu uso funções C com bastante frequência. Por exemplo, printfe scanfambos são mais rápidos que std::cine std::cout(significativamente), a fopensintaxe é muito mais amigável para os tipos binários e eles também tendem a produzir EXEs menores.

Não seja vendido neste negócio "Elegância sobre desempenho" .

user19302
fonte
Estou ciente das funções da cadeia C e também estou ciente dos problemas de desempenho (ambos os quais observei na minha pergunta). No entanto, para esta pergunta específica, estou procurando uma solução C ++ elegante.
Ashwin Nanjappa 25/10/08
11
@ Nelson LaQuet: Deixe-me adivinhar: porque strtok não é reentrante?
22468 paercebal
40
@ Nelson nunca passa string.c_str () para strtok! strtok retira a string de entrada (insere caracteres \ \ 0 'para substituir cada delimitador foudn) e c_str () retorna uma string não modificável.
Evan Teran
3
@ Nelson: Essa matriz precisa ser do tamanho str.size () + 1 em seu último comentário. Mas concordo com sua tese de que é tolice evitar funções C por razões "estéticas".
j_random_hacker
2
@paulm: Não, a lentidão dos fluxos C ++ é causada por facetas. Eles ainda são mais lentos que as funções stdio.h, mesmo quando a sincronização está desabilitada (e em strings, que não podem ser sincronizados).
Ben Voigt
42

Aqui está uma função dividida que:

  • é genérico
  • usa C ++ padrão (sem aumento)
  • aceita vários delimitadores
  • ignora fichas vazias (pode ser facilmente alterado)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Exemplo de uso:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
fonte
Você esqueceu de adicionar à lista de uso: "extremamente ineficiente"
Xander Tulip
1
@XanderTulip, você pode ser mais construtivo e explicar como ou por quê?
Marco M.
3
@XanderTulip: Presumo que você esteja se referindo a ele retornando o vetor por valor. A otimização do valor de retorno (RVO, google it) deve cuidar disso. Também no C ++ 11 você pode retornar por referência de movimento.
Joseph Garvin
3
Na verdade, isso pode ser otimizado ainda mais: em vez de .push_back (str.substr (...)) pode-se usar .emplace_back (str, start, pos - start). Dessa forma, o objeto string é construído no contêiner e, assim, evitamos uma operação de movimentação + outras travessuras realizadas pela função .substr.
Mihai Bişog 5/09/12
@zoopp yes. Boa ideia. O VS10 não tinha suporte emplace_back quando escrevi isso. Vou atualizar minha resposta. Obrigado
Marco M.
36

Eu tenho uma solução de 2 linhas para este problema:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Em vez de imprimir, você pode colocá-lo em um vetor.

rhomu
fonte
35

Mais uma maneira flexível e rápida

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Para usá-lo com um vetor de strings (Edit: Como alguém apontou para não herdar classes STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

É isso aí! E essa é apenas uma maneira de usar o tokenizer, como contar apenas palavras:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Limitado pela imaginação;)

Robert
fonte
Agradável. Sobre a Appendernota "Por que não devemos herdar uma classe das classes STL?"
Andreas Spindler
32

Aqui está uma solução simples que usa apenas a biblioteca regex padrão

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

O argumento regex permite verificar vários argumentos (espaços, vírgulas etc.)

Normalmente, eu só checo para dividir espaços e vírgulas, então também tenho esta função padrão:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

As "[\\s,]+"verificações de espaços ( \\s) e vírgulas ( ,).

Observe que se você deseja dividir em wstringvez de string,

  • mude tudo std::regexparastd::wregex
  • mude tudo sregex_token_iteratorparawsregex_token_iterator

Observe que você também pode usar o argumento string como referência, dependendo do seu compilador.

dk123
fonte
Essa teria sido minha resposta favorita, mas std :: regex está quebrado no GCC 4.8. Eles disseram que o implementaram corretamente no GCC 4.9. Eu ainda estou lhe dando o meu +1
mchiasson
1
Este é o meu favorito com pequenas alterações: o vetor retornou como referência, como você disse, e os argumentos "str" ​​e "regex" também passaram por referências. valeu.
QuantumKarl #
1
Sequências brutas são bastante úteis ao lidar com padrões regex. Dessa forma, você não precisa usar as seqüências de escape ... Você pode apenas usar R"([\s,]+)".
Sam
26

Usar std::stringstreamcomo você funciona perfeitamente bem e fazer exatamente o que você queria. Se você está apenas procurando uma maneira diferente de fazer as coisas, pode usar std::find()/ std::find_first_of()e std::string::substr().

Aqui está um exemplo:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}
KTC
fonte
Isso funciona apenas para delimitadores de caractere único. Uma simples mudança permite que ele trabalhar com multi-caractere:prev_pos = pos += delimiter.length();
David Doria
25

Se você gosta de usar o impulso, mas deseja usar uma sequência inteira como delimitador (em vez de caracteres únicos, como na maioria das soluções propostas anteriormente), você pode usar o boost_split_iterator.

Código de exemplo, incluindo modelo conveniente:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}
zerm
fonte
20

Heres uma solução regex que usa apenas a biblioteca regex padrão. (Estou um pouco enferrujado, pode haver alguns erros de sintaxe, mas essa é pelo menos a ideia geral)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}
AJMansfield
fonte
Respostas semelhantes com uma abordagem talvez melhor do regex: aqui e aqui .
dec
20

Existe uma função chamada strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}
Pratik Deoghare
fonte
3
strtoké da biblioteca padrão C, não C ++. Não é seguro usar em programas multithread. Modifica a sequência de entrada.
precisa
13
Como ele armazena o ponteiro char da primeira chamada em uma variável estática, para que nas chamadas subseqüentes quando NULL for passado, ele se lembre de qual ponteiro deve ser usado. Se um segundo encadeamento chamar strtokquando outro encadeamento ainda estiver sendo processado, esse ponteiro será substituído e os dois encadeamentos terão resultados incorretos. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
como mencionado antes strtok é insegura e mesmo em C strtok_r é recomendado para o uso
systemsfault
4
strtok_r pode ser usado se você estiver em uma seção de código que pode ser acessada. esta é a única solução de todos os itens acima que não é "ruído de linha", e é uma prova de que, exatamente, está errado com c ++
Erik Aronesty
Atualizado para que não haja objeções com base na segurança do thread dos truques em C ++.
Erik Aronesty
17

O stringstream pode ser conveniente se você precisar analisar a string por símbolos que não são de espaço:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
lukmac
fonte
14

Até agora eu usei o do Boost , mas precisava de algo que não depende disso, então cheguei a isso:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Um bom ponto é que separatorsvocê pode passar mais de um personagem.

Goran
fonte
13

Criei meu próprio usando strtok e usei o boost para dividir uma string. O melhor método que encontrei é a C ++ String Toolkit Library . É incrivelmente flexível e rápido.

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

O kit de ferramentas tem muito mais flexibilidade do que mostra este exemplo simples, mas sua utilidade na análise de uma sequência em elementos úteis é incrível.

DannyK
fonte
13

Curto e elegante

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

pode usar qualquer string como delimitador, também pode ser usado com dados binários (std :: string suporta dados binários, incluindo nulos)

usando:

auto a = split("this!!is!!!example!string", "!!");

resultado:

this
is
!example!string
user1438233
fonte
1
Eu gosto dessa solução porque ela permite que o separador seja uma sequência e não um caractere; no entanto, ele está modificando a sequência, forçando a criação de uma cópia da sequência original.
Alessandro Teruzzi
11

Eu fiz isso porque precisava de uma maneira fácil de dividir strings e c-strings ... Espero que alguém possa achar útil também. Além disso, ele não depende de tokens e você pode usar campos como delimitadores, que é outra chave de que eu precisava.

Tenho certeza de que há melhorias que podem ser feitas para melhorar ainda mais sua elegância e, por favor, faça de todos os modos

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Exemplos:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Saída:

Este
é
um
exemplo de
cstring

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Para manter entradas vazias (por padrão, os vazios serão excluídos):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

O objetivo era torná-lo semelhante ao método Split () do C #, onde a divisão de uma string é tão fácil quanto:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Espero que outra pessoa possa achar isso tão útil quanto eu.

Steve Dell
fonte
10

Que tal isso:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}
gibbz
fonte
Esta é a melhor resposta aqui, se você deseja dividir apenas um caractere delimitador. A pergunta original queria dividir em espaço em branco, significando qualquer combinação de um ou mais espaços ou tabulações consecutivas. Você realmente respondeu stackoverflow.com/questions/53849
Oktalist
10

Essa resposta pega a string e a coloca em um vetor de strings. Ele usa a biblioteca de impulso.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
NL628
fonte
9

Aqui está outra maneira de fazê-lo ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}
usuário246110
fonte
9

Eu gosto de usar os métodos boost / regex para esta tarefa, pois eles fornecem flexibilidade máxima para especificar os critérios de divisão.

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}
Marty B
fonte
9

Recentemente, tive que dividir uma palavra com camelo em subpalavras. Não há delimitadores, apenas caracteres superiores.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Por exemplo, isso divide "AQueryTrades" em "A", "Consulta" e "Negociações". A função funciona com cadeias estreitas e largas. Por respeitar o código do idioma atual, divide "RaumfahrtÜberwachungsVerordnung" em "Raumfahrt", "Überwachungs" e "Verordnung".

Nota std::upperdeve ser realmente passada como argumento do modelo de função. Então, o mais generalizado dessa função pode ser dividido em delimitadores como ",", ";"ou " "também.

Andreas Spindler
fonte
2
Houve 2 rotações. Isso é bom. Parece que meu inglês tinha muito de "alemão". No entanto, o revisionista não corrigiu dois erros menores, talvez porque eram óbvios de qualquer maneira: std::isupperpoderiam ser passados ​​como argumento, não std::upper. Segundo colocar um typenameantes do String::const_iterator.
Andreas Spindler
9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}
san45
fonte
9

Usando std::string_viewe a range-v3biblioteca de Eric Niebler :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Usando um forloop range em vez de ranges::for_eachalgoritmo:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}
Porsche9II
fonte
Sim, o intervalo para os baseados parece melhor - eu concordo
Porsche9II