3359 votes

Comment itérer sur les mots d'une chaîne de caractères ?

J'essaie d'itérer sur les mots d'une chaîne.

On peut supposer que la chaîne de caractères est composée de mots séparés par des espaces.

Notez que je ne suis pas intéressé par les fonctions de chaîne de caractères en C ou ce genre de manipulation/accès aux caractères. Par ailleurs, veuillez privilégier l'élégance à l'efficacité dans votre réponse.

La meilleure solution que j'ai pour le moment est :

#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-t-il une manière plus élégante de procéder ?

692 votes

Mec... L'élégance est juste une façon élégante de dire "l'efficacité qui est jolie" dans mon livre. N'hésitez pas à utiliser les fonctions C et les méthodes rapides pour accomplir quelque chose simplement parce que ce n'est pas contenu dans un modèle ;)

19 votes

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

0 votes

@nlaq, Sauf que vous devriez convertir votre objet chaîne de caractères en utilisant c_str(), et revenir à une chaîne de caractères si vous avez toujours besoin que ce soit une chaîne de caractères, non ?

2584voto

Evan Teran Points 42370

Je l'utilise pour séparer une chaîne de caractères par un délimiteur. La première met les résultats dans un vecteur pré-construit, la seconde retourne un nouveau vecteur.

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

Notez que cette solution ne saute pas les jetons vides, de sorte que ce qui suit trouvera 4 éléments, dont l'un est vide :

std::vector<std::string> x = split("one:two::three", ':');

0 votes

Solution élégante, j'oublie toujours ce "getline" particulier, car je ne pense pas qu'il soit conscient des guillemets et des séquences d'échappement.

0 votes

@stijn : êtes-vous en train de dire que split("one two three", ' '); retourne un vecteur avec 4 éléments ? Je ne suis pas sûr que ce soit le cas, mais je vais le tester.

0 votes

Attendez, il semble que le formatage ait supprimé certains espaces (ou je les ai oubliés) : Je parle de la chaîne "un deux trois" avec 2 espaces entre "deux" et "trois".

1510voto

Zunino Points 1855

Pour ce que cela vaut, voici une autre façon d'extraire les mots d'une chaîne de caractères en entrée, en s'appuyant uniquement sur les fonctionnalités de la bibliothèque standard. C'est un exemple de la puissance et de l'élégance de la conception de la 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"));
}

Au lieu de copier les jetons extraits vers un flux de sortie, on pourrait les insérer dans un conteneur, en utilisant la même méthode générique copy algorithme.

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

... ou créer le vector directement :

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

178 votes

Est-il possible de spécifier un délimiteur pour cela ? Comme, par exemple, la séparation par des virgules ?

7 votes

@l3dx : il semble que le paramètre " \n " est le délimiteur. Ce code est très bien, mais j'aimerais en savoir plus. Peut-être que quelqu'un pourrait expliquer chaque ligne de ce snippet ?

17 votes

@Jonathan : \n n'est pas le délimiteur dans ce cas, c'est le délimiteur pour la sortie vers cout.

874voto

ididak Points 4208

Une solution possible utilisant Boost pourrait être :

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

Cette approche pourrait être encore plus rapide que la stringstream l'approche. Et comme il s'agit d'une fonction de modèle générique, elle peut être utilisée pour diviser d'autres types de chaînes (wchar, etc. ou UTF-8) en utilisant toutes sortes de délimiteurs.

Voir le documentation pour les détails.

37 votes

La vitesse n'est pas pertinente ici, car ces deux cas sont beaucoup plus lents qu'une fonction de type strtok.

4 votes

C'est pratique et assez rapide si vous savez que la ligne ne contiendra que quelques tokens, mais si elle en contient beaucoup, vous brûlerez une tonne de mémoire (et de temps) en faisant croître le vecteur. Donc non, ce n'est pas plus rapide que la solution stringstream -- du moins pas pour un grand n, qui est le seul cas où la vitesse compte.

55 votes

Et pour ceux qui n'ont pas encore boost... bcp de copies de plus de 1000 fichiers pour cela :)

413voto

kev Points 41855
#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;
}

25 votes

Vous pouvez également séparer sur d'autres délimiteurs si vous utilisez getline dans le while par exemple, pour séparer par des virgules, utilisez while(getline(ss, buff, ',')) .

201voto

Marius Points 2008

Pour ceux qui n'aiment pas sacrifier l'efficacité au profit de la taille du code et qui considèrent l'efficacité comme une forme d'élégance, ce qui suit devrait faire l'affaire (et je pense que la classe de conteneurs de modèles est un ajout extrêmement élégant) :

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

Je choisis généralement d'utiliser std::vector<std::string> comme deuxième paramètre ( ContainerT )... mais list<> est bien plus rapide que vector<> pour les cas où l'accès direct n'est pas nécessaire, et vous pouvez même créer votre propre classe de chaîne et utiliser quelque chose comme std::list<subString>subString ne fait pas de copies pour des augmentations de vitesse incroyables.

Il est plus de deux fois plus rapide que le tokenize le plus rapide de cette page et presque 5 fois plus rapide que certains autres. De plus, avec les types de paramètres parfaits, vous pouvez éliminer toutes les copies de chaînes et de listes pour augmenter encore la vitesse.

De plus, il ne renvoie pas le résultat (extrêmement inefficace), mais transmet plutôt les jetons comme une référence, ce qui vous permet également d'accumuler des jetons en utilisant plusieurs appels si vous le souhaitez.

Enfin, il vous permet de spécifier s'il faut couper les tokens vides des résultats via un dernier paramètre facultatif.

Tout ce dont il a besoin est std::string ... le reste est optionnel. Il n'utilise pas les flux ou la bibliothèque boost, mais est suffisamment flexible pour pouvoir accepter certains de ces types étrangers naturellement.

5 votes

Je suis assez fan de cela, mais pour g++ (et probablement une bonne pratique), quiconque utilise cela voudra des typedefs et des typenames : typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Il faut ensuite substituer les types value_type et size_types en conséquence.

13 votes

Pour ceux d'entre nous pour qui les modèles et le premier commentaire sont complètement étrangers, un exemple d'utilisation de cmplete avec les inclusions requises serait formidable.

3 votes

Ahh bien, j'ai compris. J'ai mis les lignes C++ du commentaire de aws à l'intérieur du corps de fonction de tokenize(), puis j'ai édité les lignes tokens.push_back() pour changer le ContainerT::value_type en ValueType et changé (ContainerT::value_type::size_type) en (SizeType). J'ai corrigé les points sur lesquels g++ s'était plaint. Il suffit de l'invoquer comme tokenize( some_string, some_vector ) ;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X