Comment tokenize une chaîne en C++?

Java a une méthode de partage pratique:

String str = "The quick brown fox";
String[] results = str.split(" ");

Est-il un moyen facile de faire cela en C++?

376
demandé sur DavidRR 2008-09-10 16:10:25

30 réponses

votre boîtier simple peut facilement être construit en utilisant la méthode std::string::find . Cependant, jetez un oeil à Boost.Tokenizer . C'est génial. Boost a généralement quelques outils de corde très cool.

132
répondu Konrad Rudolph 2013-06-07 14:36:19

la classe Boost tokenizer peut rendre ce genre de chose très simple:

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

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

mis à jour pour 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;
    }
}
182
répondu Ferruccio 2012-05-25 13:12:19

En voici un très simple:

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

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}
161
répondu Adam Pierce 2012-12-19 22:21:45

utilisez strtok. A mon avis, il n'est pas nécessaire de construire une classe autour de la tokenisation à moins que strtok ne vous fournisse pas ce dont vous avez besoin. Peut-être pas, mais en plus de 15 ans d'écriture de différents codes d'analyse en C et C++, j'ai toujours utilisé strtok. Voici un exemple

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

quelques mises en garde (qui pourraient ne pas convenir à vos besoins). La chaîne est "détruite" dans le processus, ce qui signifie que les caractères EOS sont placés en ligne dans les points délimités. L'utilisation correcte pourrait vous obliger à prendre une non-const version de la chaîne. Vous pouvez également modifier la liste des délimiteurs milieu de l'analyser.

à mon avis, le code ci-dessus est beaucoup plus simple et plus facile à utiliser que d'écrire une classe séparée pour lui. Pour moi, c'est une de ces fonctions que la langue fournit et elle le fait bien et proprement. Il s'agit simplement d'une solution "C". C'est approprié, c'est facile, et vous n'avez pas à écrire beaucoup de code en plus :-)

102
répondu Mark 2008-09-10 13:37:33

un autre moyen rapide est d'utiliser getline . Quelque chose comme:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

si vous voulez, vous pouvez faire un simple split() méthode de retour d'un vector<string> , qui est vraiment utile.

90
répondu user35978 2010-03-04 12:12:21

vous pouvez utiliser des flux, des itérateurs, et l'algorithme de copie pour faire ceci assez directement.

#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);
}
80
répondu KeithB 2008-09-10 12:46:14

N'en déplaise aux gens, mais pour un problème simple, vous faites des choses chemin trop compliqué. Il ya beaucoup de raisons d'utiliser Boost . Mais pour quelque chose d'aussi simple, c'est comme frapper une mouche avec un sledge 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());
    }
}

par exemple (pour le cas 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] );
}

et oui, nous aurions pu diviser() retourner un nouveau vecteur plutôt que d'en passer un. C'est trivial pour les envelopper et les surcharges. Mais selon ce que je fais, je trouve souvent préférable de réutiliser des objets préexistants plutôt que d'en créer toujours de nouveaux. (Pourvu que je n'oublie pas de vider le vecteur entre les deux!)

référence: http://www.cplusplus.com/reference/string/string / .

(j'étais à l'origine en train d'écrire une réponse à la question de Doug: C++ chaînes de modification et D'extraction basées sur des séparateurs (fermé) . Mais depuis que Martin York a clos cette question avec un pointeur ici... Je vais juste généraliser mon code.)

46
répondu Mr.Ree 2017-05-23 11:47:32

Boost a une forte fonction de division: boost::algorithme::split .

exemple de programme:

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

sortie:

"a"
"b"
" c "
""
"e"
"f"
""
34
répondu Raz 2014-11-12 00:01:16

Une solution à l'aide de regex_token_iterator s:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}
25
répondu w.b 2014-12-14 10:46:47

je sais que vous avez demandé une solution C++, mais vous pourriez considérer cela utile:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

l'avantage par rapport à Boost dans cet exemple est qu'il s'agit d'une correspondance directe avec le code de votre poste.

voir plus à documentation Qt

23
répondu sivabudh 2018-04-10 15:51:32

voici un exemple de classe tokenizer qui pourrait faire ce que vous voulez""

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

exemple:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}
22
répondu vzczc 2015-07-13 06:48:11

il s'agit d'une solution STL simple (~5 lignes!) en utilisant std::find et std::find_first_not_of qui gère les répétitions du délimiteur (comme les espaces ou les périodes par exemple), ainsi que les délimiteurs de tête et de queue:

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

l'Essayer live !

18
répondu Parham 2015-03-01 22:18:06

pystring est une petite bibliothèque qui implémente un tas de fonctions de chaîne de Python, y compris la méthode 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, "-");
16
répondu dbr 2011-12-29 15:17:58

j'ai posté cette réponse pour une question similaire.

Ne pas réinventer la roue. J'ai utilisé un certain nombre de bibliothèques et la plus rapide et la plus flexible que j'ai rencontré est: C++ String Toolkit Library .

voici un exemple d'utilisation que j'ai posté ailleurs sur le 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 t("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       {
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       }
    }

    {  // parsing a string into specific variables

       std::string u("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       {
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       }
    }

    return 0;
}
9
répondu DannyK 2018-04-17 18:49:19

cochez cet exemple. Il pourrait vous aider..

#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;
}
8
répondu sohesado 2010-12-20 12:25:35

vous pouvez simplement utiliser une bibliothèque d'expressions régulières et résoudre cela en utilisant des expressions régulières.

utilise l'expression (\w+) et la variable dans \1 (ou $1 selon l'implémentation de la bibliothèque des expressions régulières).

5
répondu Fawix 2012-02-27 16:15:21

si vous êtes prêt à utiliser C, vous pouvez utiliser la fonction strtok . Vous devez prêter attention aux questions multi-threading lors de son utilisation.

4
répondu On Freund 2008-09-10 12:23:33

pour les choses simples j'utilise juste ce qui suit:

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

avertissement lâche: j'écris un logiciel de traitement de données en temps réel où les données viennent par des fichiers binaires, sockets, ou un appel API (cartes d'E/S, Appareil photo). Je n'utilise jamais cette fonction pour quelque chose de plus compliqué ou de plus critique que la lecture de fichiers de configuration externes au démarrage.

4
répondu jilles de wit 2008-09-15 15:28:39

MFC/ATL a un très joli tokenizer. De 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
4
répondu Jim In Texas 2009-03-22 02:28:16

beaucoup de suggestions trop compliquées ici. Essayez cette std simple:: string solution:

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;
}
4
répondu David919 2012-08-01 05:50:39

je pensais que c'était le but de l'opérateur >> sur string streams:

string word; sin >> word;
4
répondu Daren Thomas 2013-09-10 23:38:39

la réponse D'Adam Pierce fournit un tokenizer fait à la main avec un const char* . C'est un peu plus problématique à faire avec les itérateurs parce que incrementing a string fin itérateur est non défini . Cela dit, étant donné string str{ "The quick brown fox" } nous pouvons certainement accomplir ceci:

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 Exemple


si vous cherchez à la complexité abstraite en utilisant la fonctionnalité standard, comme sur Freund suggère strtok est une option simple:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

si vous n'avez pas accès à C++17 vous devrez remplacer data(str) comme dans cet exemple: http://ideone.com/8kAGoa

bien qu'il ne soit pas démontré dans l'exemple, strtok n'est pas nécessaire d'utiliser le même séparateur pour chaque jeton. En plus de cet avantage cependant, il y a plusieurs inconvénients:

  1. strtok ne peut pas être utilisé sur le multiple strings en même temps: soit un nullptr doit être passé pour continuer à tokenizer l'actuel string ou un nouveau char* pour tokenizer doit être passé (Il ya des mises en œuvre non-standard qui ne soutiennent ce toutefois, comme: strtok_s )
  2. pour la même raison strtok ne peut pas être utilisé sur plusieurs threads simultanément (ceci peut toutefois être défini comme une implémentation, par exemple: L'implémentation de Visual Studio est sécurisée pour les threads )
  3. Appel strtok modifie string c'est de l'exploitation, de sorte qu'il ne peut pas être utilisé sur des const string s, const char* s, ou des chaînes de caractères littérales, pour marquer ces derniers avec strtok ou pour fonctionner sur un string qui est contenu doivent être préservés, str devrait être copié, puis la copie pourrait être exploité sur

les deux méthodes précédentes ne peuvent pas générer un tokenized vector en place, ce qui signifie sans les abstraire dans une fonction d'aide qu'ils ne peuvent pas initialiser const vector<string> tokens . Que la fonctionnalité et la capacité d'accepter n'importe quel" délimiteur d'espace blanc peut être harnachée en utilisant un istream_iterator . Par exemple donné: const string str{ "The quick \tbrown \nfox" } nous pouvons faire ceci:

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

Live Exemple

la construction requise d'un istringstream pour cette option a un coût beaucoup plus élevé que les 2 options précédentes, mais ce coût est habituellement caché dans la dépense de string allocation.


Si aucun des les options ci-dessus sont suffisamment flexibles pour vos besoins de tokenization, l'option la plus flexible est d'utiliser un regex_token_iterator bien sûr avec cette flexibilité vient plus de dépenses, mais encore une fois, ce est probablement caché dans le coût d'attribution string . Disons par exemple que nous voulons tokenize basé sur des virgules Non-échappées, mangeant aussi de l'espace blanc, étant donné l'entrée suivante: const string str{ "The ,qu\,ick ,\tbrown, fox" } nous pouvons faire ceci:

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

Live Exemple

4
répondu Jonathan Mee 2017-09-22 21:45:01

Voici une approche qui vous permet de contrôler si les jetons vides sont inclus (comme strsep) ou exclus (comme 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 != '"151900920"') // 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;
}
2
répondu Darren Smith 2012-11-11 07:22:43

me semble étrange qu'avec tous les nerds américains soucieux de la vitesse, personne n'ait présenté une version qui utilise une table de recherche générée par le temps de compilation pour le délimiteur (exemple d'implémentation plus bas). Utiliser une table de recherche et des itérateurs devrait battre std::regex dans l'efficacité, si vous n'avez pas besoin de battre regex, il suffit de l'utiliser, son standard à partir de C++11 et super flexible.

certains ont déjà suggéré regex mais pour les noobs voici un exemple emballé que devrait faire exactement ce que L'OP attend:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

si nous avons besoin d'être plus rapide et accepter la contrainte que tous les caractères doivent être de 8 bits, nous pouvons faire une table de recherche au moment de compiler en utilisant la métaprogrammation:

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

avec celui en place faisant une fonction getNextToken est facile:

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

L'utiliser est aussi facile:

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

voici un exemple en direct: http://ideone.com/GKtkLQ

2
répondu odinthenerd 2014-07-27 20:06:01

il n'y a pas de moyen direct de le faire. Référez-vous à ce code projet de code source pour savoir comment construire une classe pour cela.

1
répondu Niyaz 2008-09-10 12:14:59

vous pouvez profiter de boost::make_find_iterator. Quelque chose de semblable à ceci:

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;
}
1
répondu Arash 2011-08-03 06:58:13

si la longueur maximale de la chaîne à tokeniser est connue, on peut l'exploiter et implémenter une version très rapide. Je esquisse l'idée de base ci-dessous, qui a été inspirée à la fois par strtok() et le "tableau de suffixe" - structure de données décrit Jon Bentley "Perls de programmation" 2e édition, Chapitre 15. La Classe C++ dans ce cas ne donne qu'une certaine organisation et commodité d'utilisation. L'implémentation montrée peut être facilement étendue pour supprimer les espaces de tête et de fuite les personnages dans les jetons.

fondamentalement, on peut remplacer les caractères séparateurs par des caractères '\0'à terminaison de chaîne et mettre des pointeurs vers les jetons avec la chaîne modifiée. Dans le cas extrême où la chaîne se compose uniquement de séparateurs, on obtient la longueur de la chaîne plus 1 tokens vides résultant. C'est pratique pour dupliquer la chaîne à modifier.

fichier d'en-Tête:

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

fichier de mise en œuvre:

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 != '"151910920"' );

    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 == '"151910920"' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '"151910920"';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

, Un scénario d'utilisation serait:

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

sortie:

Item1

Item2
Item3
0
répondu Angel Sinigersky 2011-05-15 20:47:07

boost::tokenizer est votre ami, mais envisager de rendre votre code portable avec référence à l'internationalisation (i18n) questions en utilisant wstring / wchar_t au lieu de l'héritage string / char types.

#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;
}
0
répondu jochenleidner 2012-07-16 01:19:26

code C++ Simple (standard C++98), accepte les délimiteurs multiples (spécifiés dans une norme std::string), utilise uniquement des vecteurs, des chaînes et des itérateurs.

#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;
}
0
répondu vsoftco 2013-12-15 01:32:53
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
/// 
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end(), i;
    iter sep_b = sep.begin(), sep_e = sep.end();

    // search through s
    while (b != e){
        i = search(b, e, sep_b, sep_e);

        // no more separator found
        if (i == e){
            // it's not an empty string
            if (b != e)
                v.push_back(string(b, e));
            break;
        }
        else if (i == b){
            // the separator is found and right at the beginning
            // in this case, we need to move on and search for the
            // next separator
            b = i + sep.length();
        }
        else{
            // found the separator
            v.push_back(string(b, i));
            b = i;
        }
    }
}

la bibliothèque boost est bonne, mais ils ne sont pas toujours disponibles. Faire ce genre de choses à la main est aussi un bon exercice de cerveau. Ici, nous n'utilisons que l'algorithme std::search() de la STL, voir le code ci-dessus.

0
répondu Murphy78 2014-02-25 06:40:51