Comment puis-je tokenize une chaîne en C ++?


414

Java a une méthode de partage pratique:

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

Existe-t-il un moyen simple de le faire en C ++?


172
Je ne peux pas croire que cette tâche de routine soit un tel casse-tête en C ++
wfbarksdale

6
Ce n'est pas un casse-tête en c ++ - il existe différentes façons d'y parvenir. les programmeurs sont moins conscients du c ++ que du c # - c'est sur le marketing et les investissements ... voyez ceci pour diverses options c ++ pour obtenir le même: cplusplus.com/faq/sequences/strings/split
hB0

9
@ hB0 passe par beaucoup de réponses aux questions et ne décide toujours pas des moyens est un casse-tête. l'un a besoin de cette bibliothèque, l'autre est juste pour les espaces, l'autre ne gère pas les espaces ..
Paschalis

1
Duplication possible de Fractionner une chaîne en C ++?
KOB

2
Pourquoi tout en C ++ doit-il être difficile?
Wael Assaf

Réponses:


145

Les algorithmes de bibliothèque standard C ++ sont assez universellement basés autour d'itérateurs plutôt que de conteneurs concrets. Malheureusement, cela rend difficile la fourniture d'une splitfonction de type Java dans la bibliothèque standard C ++, même si personne ne prétend que ce serait pratique. Mais quel serait son type de retour?std::vector<std::basic_string<…>>? Peut-être, mais alors nous sommes obligés d'effectuer des allocations (potentiellement redondantes et coûteuses).

Au lieu de cela, C ++ offre une multitude de façons de diviser les chaînes en fonction de délimiteurs arbitrairement complexes, mais aucune d'entre elles n'est aussi bien encapsulée que dans d'autres langages. Les nombreuses façons de remplir des articles de blog entiers .

À sa plus simple expression, vous pouvez répéter l’utilisation std::string::findjusqu’à ce que vous frappiez std::string::nposet extraire le contenu à l’aide destd::string::substr .

Une version plus fluide (et idiomatique, mais basique) pour le fractionnement sur un espace blanc utiliserait std::istringstream:

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

À l'aide de std::istream_iterators , le contenu du flux de chaînes peut également être copié dans un vecteur à l'aide de son constructeur de plage d'itérateur.

Plusieurs bibliothèques (telles que Boost.Tokenizer ) proposent des tokenisers spécifiques.

Un fractionnement plus avancé nécessite des expressions régulières. C ++ fournit notamment std::regex_token_iteratorà cet effet:

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

53
Malheureusement, le boost n'est pas toujours disponible pour tous les projets. Je vais devoir chercher une réponse non boost.
FuzzyBunnySlippers

36
Tous les projets ne sont pas ouverts à "l'open source". Je travaille dans des secteurs fortement réglementés. Ce n'est pas vraiment un problème. C'est juste une réalité de la vie. Boost n'est pas disponible partout.
FuzzyBunnySlippers

5
@NonlinearIdeas L'autre question / réponse ne concernait pas du tout les projets Open Source. Il en va de même pour tout projet. Cela dit, je comprends bien sûr les normes restreintes telles que MISRA C, mais il est entendu que vous construisez tout à partir de zéro (à moins que vous ne trouviez une bibliothèque conforme - une rareté). Quoi qu'il en soit, le fait n'est pas que «Boost n'est pas disponible» - c'est que vous avez des exigences spéciales pour lesquelles presque toutes les réponses à usage général seraient inappropriées.
Konrad Rudolph

1
@NonlinearIdeas Exemple, les autres réponses non-Boost ne sont pas non plus conformes à MISRA.
Konrad Rudolph

3
@Dmitry Qu'est-ce que "STL barf"?! Et toute la communauté est très favorable au remplacement du préprocesseur C - en fait, il y a des propositions pour le faire. Mais votre suggestion d'utiliser PHP ou un autre langage à la place serait un énorme pas en arrière.
Konrad Rudolph

188

le classe de tokenizer Boost peut rendre ce genre de chose assez 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;
    }
}

1
Bon, j'ai récemment utilisé ça. Mon compilateur Visual Studio a un étrange bourdonnement jusqu'à ce que j'utilise un espace pour séparer les deux caractères ">" avant les jetons (texte, sep): (erreur C2947: attendre que '>' termine le modèle-liste d'arguments, trouvé '> > ')
AndyUK

@AndyUK oui, sans l'espace, le compilateur le traite comme un opérateur d'extraction plutôt que comme deux modèles de fermeture.
EnabrenTane

Théoriquement, cela a été corrigé en C ++ 0x
David Souther

3
méfiez-vous des troisièmes paramètres du char_separatorconstructeur ( drop_empty_tokensc'est la valeur par défaut, l'alternative l'est keep_empty_tokens).
Benoit

5
@puk - C'est un suffixe couramment utilisé pour les fichiers d'en-tête C ++. (comme .hpour les en-têtes C)
Ferruccio

167

En voici une 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;
}

dois-je ajouter un prototype pour cette méthode dans le fichier .h?
Suhrob Samiev

5
Ce n'est pas exactement la «meilleure» réponse car elle utilise toujours un littéral de chaîne qui est le tableau de caractères constants en C simple. Je crois que le questionneur demandait s'il pouvait symboliser une chaîne C ++ qui est de type "chaîne" introduite par ce dernier.
Vijay Kumar Kanta

Cela nécessite une nouvelle réponse car je soupçonne fortement que l'inclusion d'expressions régulières dans C ++ 11 a changé la meilleure réponse.
Omnifarious

114

Utilisez strtok. À mon avis, il n'est pas nécessaire de créer une classe autour de la tokenisation à moins que strtok ne vous fournisse pas ce dont vous avez besoin. Ce n'est peut-être pas le cas, mais en 15 ans et plus d'écriture de divers 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" au cours du processus, ce qui signifie que les caractères EOS sont placés en ligne dans les points de délimitation. Une utilisation correcte peut vous obliger à créer une version non constante de la chaîne. Vous pouvez également modifier la liste des délimiteurs en cours d'analyse.

À mon avis, le code ci-dessus est beaucoup plus simple et plus facile à utiliser que d'écrire une classe distincte pour lui. Pour moi, c'est l'une de ces fonctions que le langage fournit et il le fait bien et proprement. C'est simplement une solution "basée sur C". C'est approprié, c'est facile, et vous n'avez pas besoin d'écrire beaucoup de code supplémentaire :-)


42
Non pas que je n'aime pas C, mais strtok n'est pas thread-safe, et vous devez être certain que la chaîne que vous lui envoyez contient un caractère nul pour éviter un éventuel débordement de tampon.
tloach

11
Il y a strtok_r, mais c'était une question C ++.
contrat du professeur Falken a été rompu

3
@tloach: dans le compilateur MS C ++, strtok est sûr pour les threads car la variable statique interne est créée sur le TLS (stockage local des threads) (en fait, cela dépend du compilateur)
Ahmed Said

3
@ahmed: thread safe signifie plus que simplement pouvoir exécuter la fonction deux fois dans différents threads. Dans ce cas, si le thread est modifié pendant l'exécution de strtok, il est possible que la chaîne soit valide pendant toute la durée de l'exécution de strtok, mais strtok ne fonctionnera pas correctement car la chaîne a changé, elle a déjà dépassé le caractère nul et va continuez à lire la mémoire jusqu'à ce qu'elle obtienne une violation de sécurité ou trouve un caractère nul. Il s'agit d'un problème avec les fonctions de chaîne C d'origine, si vous ne spécifiez pas de longueur quelque part, vous rencontrez des problèmes.
tloach

4
strtok nécessite un pointeur vers un tableau de caractères à terminaison nulle non const, ce qui n'est pas une créature courante à trouver dans le code c ++ ... quelle est votre façon préférée de la convertir à partir d'une chaîne std ::?
fuzzyTew

106

Un autre moyen rapide consiste à utiliser getline. Quelque chose comme:

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

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

Si vous le souhaitez, vous pouvez créer une split()méthode simple renvoyant un vector<string>, ce qui est vraiment utile.


2
J'ai eu des problèmes en utilisant cette technique avec des caractères 0x0A dans la chaîne, ce qui a fait que la boucle while se ferme prématurément. Sinon, c'est une belle solution simple et rapide.
Ryan H.

4
C'est bien, mais il suffit de garder à l'esprit qu'en faisant cela, le délimiteur par défaut '\ n' n'est pas pris en compte. Cet exemple fonctionnera, mais si vous utilisez quelque chose comme: while (getline (inFile, word, '')) où inFile est un objet ifstream contenant plusieurs lignes, vous obtiendrez des résultats amusants.
hackrock

c'est dommage que getline retourne le flux plutôt que la chaîne, ce qui le rend inutilisable dans les listes d'initialisation sans stockage temporaire
fuzzyTew

1
Cool! Pas de boost et C ++ 11, une bonne solution pour ces projets hérités là-bas!
Deqing

1
C'EST la réponse, le nom de la fonction est juste un peu gênant.
Nils

82

Vous pouvez utiliser des flux, des itérateurs et l'algorithme de copie pour le faire 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);
}

17
Je trouve ces std :: irritants à lire .. pourquoi ne pas utiliser "using"?
user35978

80
@Vadi: parce que modifier le post de quelqu'un d'autre est assez intrusif. @pheze: Je préfère laisser la stdfaçon dont je sais d'où vient mon objet, ce n'est qu'une question de style.
Matthieu M.

7
Je comprends votre raison et je pense que c'est en fait un bon choix si cela fonctionne pour vous, mais d'un point de vue pédagogique, je suis d'accord avec Pheze. Il est plus facile de lire et de comprendre un exemple complètement étranger comme celui-ci avec un "using namespace std" en haut car cela nécessite moins d'effort pour interpréter les lignes suivantes ... surtout dans ce cas parce que tout provient de la bibliothèque standard. Vous pouvez rendre la lecture facile et évidente d'où viennent les objets par une série de "using std :: string;" etc. D'autant plus que la fonction est si courte.
cheshirekow

61
Bien que les préfixes "std ::" soient irritants ou laids, il est préférable de les inclure dans le code d'exemple afin qu'il soit complètement clair d'où viennent ces fonctions. S'ils vous dérangent, il est trivial de les remplacer par une «utilisation» après avoir volé l'exemple et revendiqué le vôtre.
dlchambers

20
oui! ce qu'il a dit! les meilleures pratiques consistent à utiliser le préfixe std. Toute grande base de code aura sans aucun doute ses propres bibliothèques et espaces de noms et l'utilisation de "using namespace std" vous donnera des maux de tête lorsque vous commencerez à provoquer des conflits d'espace de noms.
Miek

48

Pas de gens d'infraction, mais pour un problème simple, vous faites des choses façon trop compliquée. Il existe de nombreuses raisons d'utiliser Boost . Mais pour quelque chose d'aussi simple, c'est comme frapper une mouche avec un traîneau 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 split () retourner un nouveau vecteur plutôt que d'en passer un. Il est trivial d'envelopper et de surcharger. 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. (Tant que je n'oublie pas de vider le vecteur entre les deux!)

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

(J'écrivais à l'origine une réponse à la question de Doug: modification et extraction de chaînes C ++ basées sur des séparateurs (fermé) . Mais puisque Martin York a fermé cette question avec un pointeur ici ... je vais simplement généraliser mon code.)


12
Pourquoi définir une macro que vous n'utilisez qu'en un seul endroit. Et comment votre UASSERT est-il meilleur que l'assertion standard? Diviser la comparaison en 3 jetons comme celui-ci ne fait rien d'autre que nécessiter plus de virgules que vous n'en auriez besoin autrement.
crelbor

1
Peut-être que la macro UASSERT montre (dans le message d'erreur) la relation réelle entre (et les valeurs de) les deux valeurs comparées? C'est en fait une assez bonne idée, à mon humble avis.
GhassanPL

10
Pouah, pourquoi la std::stringclasse n'inclut-elle pas une fonction split ()?
M. Shickadance

Je pense que la dernière ligne de la boucle while devrait être start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());et la boucle while devrait être while (start != string::npos). De plus, je vérifie la sous-chaîne pour m'assurer qu'elle n'est pas vide avant de l'insérer dans le vecteur.
John K

@JohnK Si l'entrée a deux délimiteurs consécutifs, alors clairement la chaîne entre eux est vide et doit être insérée dans le vecteur. Si des valeurs vides ne sont pas acceptables pour un usage particulier, c'est autre chose, mais de telles contraintes à mon humble avis devraient être appliquées en dehors de ce type de fonctions à usage très général.
Lauri Nurmi

46

Une solution utilisant 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;
    }
}

5
Cela devrait être la réponse la mieux classée. C'est la bonne façon de le faire en C ++> = 11.
Omnifarious

1
Je suis content d'avoir fait défiler jusqu'à cette réponse (il n'y avait actuellement que 9 votes positifs). C'est exactement à quoi devrait ressembler un code C ++ 11 pour cette tâche!
YePhIcK

Excellente réponse qui ne repose pas sur des bibliothèques externes et utilise des bibliothèques déjà disponibles
Andrew

1
Grande réponse, donnant le plus de flexibilité dans les délimiteurs. Quelques mises en garde: l'utilisation de \ s + regex évite les jetons vides au milieu du texte, mais donne un premier jeton vide si le texte commence par un espace. En outre, l'expression régulière semble lente: sur mon ordinateur portable, pour 20 Mo de texte aléatoire, cela prend 0,6 seconde, contre 0,014 seconde pour strtok, strsep ou la réponse de Parham en utilisant str.find_first_of, ou 0,027 seconde pour Perl ou 0,021 seconde pour Python . Pour un texte court, la vitesse peut ne pas être un problème.
Mark Gates

2
Ok, peut-être que ça a l'air cool, mais c'est clairement une surutilisation des expressions régulières. Raisonnable uniquement si vous ne vous souciez pas des performances.
Marek R

35

Boost a une forte fonction de partage: boost :: algorithm :: 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;
}

Production:

"a"
"b"
" c "
""
"e"
"f"
""

26

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'un mappage direct un à un avec le code de votre message.

En savoir plus sur la documentation Qt


22

Voici un exemple de classe de 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());
}

19

Il s'agit d'une solution simple en STL (~ 5 lignes!) Utilisant std::findet std::find_first_not_ofqui gère les répétitions du délimiteur (comme les espaces ou les périodes par exemple), ainsi que les délimiteurs de début et de fin:

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

Essayez-le en direct !


3
C'est un bon mais je pense que vous devez utiliser find_first_of () au lieu de find () pour que cela fonctionne correctement avec plusieurs délimiteurs.

2
@ user755921 plusieurs délimiteurs sont ignorés lors de la recherche de la position de départ avec find_first_not_of.
Débutant

16

pystring est une petite bibliothèque qui implémente un tas de fonctions de chaîne de Python, y compris la méthode de fractionnement:

#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, "-");

3
Wow, vous avez répondu à ma question immédiate et à de nombreuses questions futures. Je comprends que c ++ est puissant. Mais lorsque le fractionnement d'une chaîne entraîne un code source comme les réponses ci-dessus, c'est clairement décourageant. J'aimerais connaître d'autres bibliothèques comme celle-ci qui abaissent les commodités des langages de niveau supérieur.
Ross

wow, vous venez de faire ma journée sérieusement !! ne connaissait pas pystring. cela va me faire gagner beaucoup de temps!
accraze

11

J'ai posté cette réponse pour une question similaire.
Ne réinventez pas la roue. J'ai utilisé un certain nombre de bibliothèques et la plus rapide et la plus flexible que j'ai rencontrée est la suivante: C ++ String Toolkit Library .

Voici un exemple d'utilisation que j'ai publié 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 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;
}

8

Vérifiez cet exemple. Cela 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;
}

1
Je feraiswhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix

6

MFC / ATL a un très joli tokenizer. Depuis 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

1
Cette fonction Tokenize () sautera les jetons vides, par exemple, s'il y a une sous-chaîne "%%" dans la chaîne principale, aucun jeton vide n'est retourné. Il est ignoré.
Sheen

4

Si vous êtes prêt à utiliser C, vous pouvez utiliser la fonction strtok . Vous devez faire attention aux problèmes de multi-threading lors de son utilisation.


3
Notez que strtok modifie la chaîne que vous vérifiez, vous ne pouvez donc pas l'utiliser sur des chaînes const char * sans faire de copie.
Graeme Perrow

9
Le problème du multithreading est que strtok utilise une variable globale pour garder une trace de l'endroit où il se trouve, donc si vous avez deux threads qui utilisent chacun strtok, vous obtiendrez un comportement non défini.
JohnMcG

@JohnMcG Ou utilisez simplement strtok_sce qui est essentiellement strtokavec un passage d'état explicite.
Matthias

4

Pour des trucs simples, j'utilise simplement 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 arrivent via des fichiers binaires, des sockets ou un appel API (cartes d'E / S, caméras). Je n'utilise jamais cette fonction pour quelque chose de plus compliqué ou de temps critique que la lecture de fichiers de configuration externes au démarrage.


4

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

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


+1 pour suggérer l'expression régulière, si vous n'avez pas besoin de vitesse de distorsion, c'est la solution la plus flexible, pas encore prise en charge partout mais avec le temps, cela deviendra moins important.
odinthenerd

+1 de moi, j'ai juste essayé <regex> en c ++ 11. Si simple et élégant
StahlRat

4

Beaucoup de suggestions trop compliquées ici. Essayez cette solution simple 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;
}

4

Je pensais que c'était pour cela que l' >>opérateur sur les flux de chaînes:

string word; sin >> word;

1
Ma faute pour avoir donné un mauvais exemple (trop simple). Pour autant que je sache, cela ne fonctionne que lorsque votre délimiteur est un espace blanc.
Bill the Lizard

4

La réponse d'Adam Pierce fournit un jeton tourné à la main prenant en a const char*. C'est un peu plus problématique avec les itérateurs car l' incrémentation de l' stringitérateur de fin n'est pas définie . Cela dit, étant donné que string str{ "The quick brown fox" }nous pouvons certainement y parvenir:

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


Si vous recherchez une complexité abstraite en utilisant des fonctionnalités standard, comme le suggère On Freund, strtok c'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 que cela ne soit pas démontré dans l'exemple, il strtokn'est pas nécessaire d'utiliser le même délimiteur pour chaque jeton. Parallèlement à cet avantage, il existe cependant plusieurs inconvénients:

  1. strtokne peut pas être utilisé sur plusieurs stringsen même temps: Soit un nullptrdoit être adopté pour continuer tokenizing le courant stringou une nouvelle char*à tokenize doit être adopté (il y a quelques implémentations non standard qui ne supportent ce cependant, comme: strtok_s)
  2. Pour la même raison, strtokne peut pas être utilisé sur plusieurs threads simultanément (cela peut cependant être défini par l'implémentation, par exemple: l'implémentation de Visual Studio est thread-safe )
  3. L'appel strtokmodifie le stringsystème sur lequel il fonctionne, il ne peut donc pas être utilisé sur des chaînes const strings, const char*s ou littérales, pour symboliser l'un d'entre eux avec strtokou pour fonctionner sur un stringcontenu dont le contenu doit être conservé, strdoit être copié, puis la copie peut être opéré

nous fournit split_viewpour tokeniser les chaînes, de manière non destructive: https://topanswers.xyz/cplusplus?q=749#a874


Les méthodes précédentes ne peuvent pas générer un vectorin- tokenized , c'est-à-dire sans les abstraire dans une fonction d'assistance qu'ils ne peuvent pas initialiser const vector<string> tokens. Cette fonctionnalité et la capacité d'accepter tout délimiteur d'espace blanc peuvent être exploitées à l'aide d'un istream_iterator. Par exemple donné: const string str{ "The quick \tbrown \nfox" }nous pouvons le faire:

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

Live Example

La construction requise d'un istringstreampour cette option a un coût beaucoup plus élevé que les 2 options précédentes, mais ce coût est généralement caché dans les frais d' stringallocation.


Si aucune des options ci-dessus n'est suffisamment flexible pour vos besoins de tokenisation, l'option la plus flexible utilise regex_token_iteratorbien sûr une flexibilité qui entraîne des dépenses plus importantes, mais là encore, cela est probablement caché dans le stringcoût d'allocation. Supposons par exemple que nous souhaitons effectuer une tokenisation basée sur des virgules non échappées, en mangeant également des espaces blancs, étant donné l'entrée suivante: const string str{ "The ,qu\\,ick ,\tbrown, fox" }nous pouvons le faire:

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

Live Example


strtok_sest d'ailleurs la norme C11. strtok_rest une norme POSIX2001. Entre les deux, il existe une version standard réentrante strtokpour la plupart des plateformes.
Andon M. Coleman

@ AndonM.Coleman Mais c'est une question c ++ , et en C ++ #include <cstring>ne comprend que la version c99 de strtok. Donc, mon hypothèse est que vous fournissez simplement ce commentaire comme matériel de support, démontrant la disponibilité spécifique des implémentations des strtokextensions?
Jonathan Mee

1
Simplement que ce n'est pas aussi inhabituel que les gens pourraient le croire. strtok_sest fourni par C11 et en tant qu'extension autonome dans le runtime C de Microsoft. Il y a un peu d'histoire ici où les _sfonctions de Microsoft sont devenues la norme C.
Andon M. Coleman

@ AndonM.Coleman D'accord, je suis avec vous. Évidemment, si c'est dans la norme C11, l'interface et l'implémentation sont soumises à des contraintes qui nécessitent un comportement identique indépendamment de la plate-forme. Maintenant, le seul problème est de s'assurer que la fonction C11 est disponible pour nous sur toutes les plateformes. Espérons que la norme C11 sera quelque chose que C ++ 17 ou C ++ 20 choisira de ramasser.
Jonathan Mee

3

Je sais que cette question a déjà reçu une réponse, mais je souhaite y contribuer. Peut-être que ma solution est un peu simple, mais voici ce que j'ai trouvé:

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

Veuillez commenter s'il existe une meilleure approche de quelque chose dans mon code ou si quelque chose ne va pas.

MISE À JOUR: ajout d'un séparateur générique


J'ai utilisé votre solution de la foule :) Puis-je modifier votre code pour ajouter un séparateur?
Zac

1
@Zac content que vous l'ayez aimé et bien sûr vous pouvez le modifier ... ajoutez simplement une section de mise à jour en gras à ma réponse ...
NutCracker

2

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 != '\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;
}

2

Il me semble étrange qu'avec tous les nerds soucieux de la vitesse ici sur SO, personne n'a 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 en termes d'efficacité, si vous n'avez pas besoin de battre regex, utilisez-le simplement, son standard en C ++ 11 et super flexible.

Certains ont déjà suggéré l'expression régulière, mais pour les noobs, voici un exemple compact qui 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 devons être plus rapides et accepter la contrainte que tous les caractères doivent être 8 bits, nous pouvons faire une table de recherche au moment de la compilation 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 cela en place, faire une getNextTokenfonction 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);
}

Son utilisation est également simple:

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


1
Est-il possible de tokennize avec un délimiteur de chaîne?
Galigator

cette version n'est optimisée que pour les délimiteurs à un seul caractère, l'utilisation d'une table de recherche n'est pas adaptée aux délimiteurs à plusieurs caractères (chaîne), ce qui rend son regex plus difficile à battre en termes d'efficacité.
odinthenerd

1

vous pouvez profiter de boost :: make_find_iterator. Quelque chose de similaire à 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

Voici mon couteau Swiss® Army de jetons de chaîne pour séparer les chaînes par des espaces, en tenant compte des chaînes enveloppées de guillemets simples et doubles ainsi que de supprimer ces caractères des résultats. J'ai utilisé RegexBuddy 4.x pour générer la plupart des extraits de code, mais j'ai ajouté une gestion personnalisée pour supprimer les devis et quelques autres choses.

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

1
Les votes (descendants) peuvent être aussi constructifs que les votes positifs, mais pas lorsque vous ne laissez pas de commentaires sur les raisons pour lesquelles ...
kayleeFrye_onDeck

1
Je vous ai égalisé, mais cela pourrait être parce que le code semble assez intimidant pour le programmeur googler `` comment diviser une chaîne '' en particulier sans documentation
mattshu

Merci @mattshu! Est-ce que ce sont les segments d'expression régulière qui le rendent intimidant ou autre chose?
kayleeFrye_onDeck

0

Si la longueur maximale de la chaîne d'entrée à tokeniser est connue, on peut l'exploiter et implémenter une version très rapide. J'esquisse l'idée de base ci-dessous, qui a été inspirée à la fois par strtok () et la structure de données "tableau de suffixes" décrit la 2ème édition de "Programmation Perls" de Jon Bentley, chapitre 15. La classe C ++ dans ce cas ne donne qu'une certaine organisation et commodité utile. L'implémentation illustrée peut être facilement étendue pour supprimer les caractères d'espacement avant et arrière dans les jetons.

Fondamentalement, on peut remplacer les caractères de séparation par des caractères «\ 0» de terminaison de chaîne et définir des pointeurs sur 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 chaîne plus 1 jetons vides résultants. Il est pratique de dupliquer la chaîne à modifier.

En tête de fichier:

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 d'implémentation:

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

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

production:

Item1

Item2
Item3

0

boost::tokenizerest votre ami, mais envisagez de rendre votre code portable en référence aux problèmes d'internationalisation (i18n) en utilisant wstring/ wchar_tau lieu de l'héritage string/ chartypes.

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

"hérité" n'est certainement pas correct et wchar_test un type dépendant de la mise en œuvre horrible que personne ne devrait utiliser sauf si absolument nécessaire.
CoffeeandCode

L'utilisation de wchar_t ne résout pas automatiquement les problèmes d'i18n. Vous utilisez des encodages pour résoudre ce problème. Si vous divisez une chaîne par un délimiteur, cela implique que le délimiteur n'entre pas en collision avec le contenu codé d'un jeton à l'intérieur de la chaîne. Il peut être nécessaire de s'échapper, etc. wchar_t n'est pas une solution magique à cela.
yonil

0

Le code C ++ simple (C ++ 98 standard), accepte plusieurs délimiteurs (spécifiés dans une chaîne std ::), 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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.