Comment répéter les mots d'une chaîne?

2989

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

On peut supposer que la chaîne 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 C ou ce genre de manipulation / accès aux caractères. Veuillez également donner la priorité à l'élégance sur l'efficacité dans votre réponse.

La meilleure solution que j'ai en ce 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?

Ashwin Nanjappa
la source
617
Mec ... L'élégance est juste une façon élégante de dire "efficacité-qui-semble-jolie" dans mon livre. N'hésitez pas à utiliser les fonctions C et les méthodes rapides pour accomplir quoi que ce soit simplement parce qu'il n'est pas contenu dans un modèle;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon
21
@Eduardo: c'est faux aussi ... vous devez tester iss entre essayer de diffuser une autre valeur et utiliser cette valeur, c'eststring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Diverses options en C ++ pour faire cela par défaut: cplusplus.com/faq/sequences/strings/split
hB0
14
L'élégance ne se limite pas à une simple efficacité. Les attributs élégants incluent un faible nombre de lignes et une lisibilité élevée. IMHO Elegance n'est pas un proxy pour l'efficacité mais la maintenabilité.
Matt

Réponses:

1369

Pour ce que ça vaut, voici une autre façon d'extraire des jetons d'une chaîne d'entrée, en ne s'appuyant que sur les fonctionnalités de bibliothèque standard. C'est un exemple de la puissance et de l'élégance derrière le design 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 dans un flux de sortie, on pourrait les insérer dans un conteneur, en utilisant le même copyalgorithme générique .

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

... ou créez vectordirectement:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
la source
164
Est-il possible de spécifier un délimiteur pour cela? Comme par exemple le fractionnement de virgules?
l3dx
15
@Jonathan: \ n n'est pas le délimiteur dans ce cas, c'est le suppresseur pour la sortie vers cout.
huy
772
C'est une mauvaise solution car elle ne prend aucun autre délimiteur, donc non évolutive et non maintenable.
HelloWorld
37
En fait, cela peut très bien fonctionner avec d'autres délimiteurs (même si en faire est un peu moche). Vous créez une facette de type c qui classe les délimiteurs souhaités en tant qu'espace, créez un environnement local contenant cette facette, puis imprégnez le flux de chaînes de cet environnement local avant d'extraire des chaînes.
Jerry Coffin
53
@Kinderchocolate "La chaîne peut être supposée être composée de mots séparés par des espaces" - Hmm, cela ne semble pas être une mauvaise solution au problème de la question. "pas évolutif et non maintenable" - Hah, chouette.
Christian Rau
2426

J'utilise ceci pour diviser la chaîne par un délimiteur. Le premier met les résultats dans un vecteur préconstruit, le second renvoie 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 n'ignore pas les jetons vides, donc les éléments suivants trouveront 4 éléments, dont l'un est vide:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran
la source
86
Afin d'éviter qu'il ne saute des jetons vides, empty()vérifiez:if (!item.empty()) elems.push_back(item)
0x499602D2
11
Que diriez-vous du délim contient deux caractères comme ->?
herohuyongtao
7
@herohuyongtao, cette solution ne fonctionne que pour les délimiteurs à caractère unique.
Evan Teran
4
@JeshwanthKumarNK, ce n'est pas nécessaire, mais cela vous permet de faire des choses comme passer le résultat directement à une fonction comme celle-ci: f(split(s, d, v))tout en bénéficiant d'un pré-alloué vectorsi vous le souhaitez.
Evan Teran
8
Avertissement: split ("one: two :: three", ':') et split ("one: two :: three:", ':') renvoient la même valeur.
dshin
834

Une solution possible en 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 l' stringstreamapproche. 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.

Consultez la documentation pour plus de détails.

ididak
la source
35
La vitesse n'est pas pertinente ici, car ces deux cas sont beaucoup plus lents qu'une fonction de type strtok.
Tom
45
Et pour ceux qui n'ont pas encore de boost ... bcp copie plus de 1000 fichiers pour ça :)
Roman Starkov
12
Attention, quand on lui donne une chaîne vide (""), cette méthode retourne un vecteur contenant la "" chaîne. Ajoutez donc un "if (! String_to_split.empty ())" avant le fractionnement.
Offirmo
29
Les développeurs @Ian Embedded n'utilisent pas tous boost.
ACK_stoverflow
31
en complément: j'utilise boost uniquement quand je le dois, normalement je préfère ajouter à ma propre bibliothèque de code qui est autonome et portable afin que je puisse obtenir un petit code spécifique précis, qui accomplit un objectif donné. De cette façon, le code est non public, performant, trivial et portable. Boost a sa place mais je dirais que c'est un peu exagéré pour tokeniser les cordes: vous ne feriez pas transporter toute votre maison chez une firme d'ingénierie pour obtenir un nouveau clou enfoncé dans le mur pour accrocher une photo ... ils peuvent le faire extrêmement bien, mais les avantages dépassent de loin les inconvénients.
GMasucci
364
#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
la source
13
Vous pouvez également diviser sur d'autres délimiteurs si vous utilisez getlinedans la whilecondition par exemple pour diviser par des virgules, utilisez while(getline(ss, buff, ',')).
Ali
181

Pour ceux avec qui il n'est pas bon de sacrifier toute l'efficacité pour la taille du code et de voir "efficace" comme un type d'élégance, ce qui suit devrait frapper un point idéal (et je pense que la classe de conteneur de modèle est un ajout incroyablement é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 les std::vector<std::string>types comme deuxième paramètre ( ContainerT) ... mais list<>c'est beaucoup plus rapide que vector<>lorsque l'accès direct n'est pas nécessaire, et vous pouvez même créer votre propre classe de chaînes et utiliser quelque chose comme std::list<subString>subString ne fait aucune copie pour une vitesse incroyable augmente.

C'est plus du double de la jeton le plus rapide de cette page et presque 5 fois plus rapide que d'autres. De plus, avec les types de paramètres parfaits, vous pouvez éliminer toutes les copies de chaînes et de listes pour des augmentations de vitesse supplémentaires.

De plus, il ne fait pas le retour de résultat (extrêmement inefficace), mais plutôt il passe les jetons comme référence, vous permettant ainsi de créer des jetons en utilisant plusieurs appels si vous le souhaitez.

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

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

Marius
la source
5
Je suis assez fan de cela, mais pour g ++ (et probablement une bonne pratique), quiconque l'utilisera voudra des typedefs et des typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Ensuite, remplacez value_type et size_types en conséquence.
Aws
11
Pour ceux d'entre nous pour qui les trucs de modèle et le premier commentaire sont complètement étrangers, un exemple d'utilisation cmplete avec les inclus requis serait charmant.
Wes Miller
3
Ahh bien, je l'ai compris. J'ai mis les lignes C ++ du commentaire de aws dans le corps de la 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 ( Type de taille). Correction des bits sur lesquels g ++ se plaignait. Il suffit de l'invoquer comme tokenize (some_string, some_vector);
Wes Miller
2
En plus d'exécuter quelques tests de performances sur des exemples de données, je les ai principalement réduits au minimum d'instructions et au moins de copies de mémoire possibles grâce à l'utilisation d'une classe de sous-chaîne qui ne fait référence qu'aux décalages / longueurs dans d'autres chaînes. (J'ai roulé le mien, mais il existe d'autres implémentations). Malheureusement, il n'y a pas grand-chose d'autre à faire pour améliorer cela, mais des augmentations progressives étaient possibles.
Marius
3
C'est la sortie correcte pour quand trimEmpty = true. Gardez à l'esprit que ce "abo"n'est pas un délimiteur dans cette réponse, mais la liste des caractères du délimiteur. Il serait simple de le modifier pour prendre une seule chaîne de caractères de délimitation (je pense que cela str.find_first_ofdevrait changer str.find_first, mais je peux me tromper ... je ne peux pas tester)
Marius
158

Voici une autre solution. Il est compact et raisonnablement efficace:

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

Il peut facilement être modélisé pour gérer les séparateurs de chaînes, les chaînes larges, etc.

Notez que le fractionnement ""entraîne une seule chaîne vide et le fractionnement ","(c'est-à-dire sep) entraîne deux chaînes vides.

Il peut également être facilement étendu pour ignorer les jetons vides:

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

Si le fractionnement d'une chaîne à plusieurs délimiteurs tout en ignorant les jetons vides est souhaité, cette version peut être utilisée:

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
la source
10
La première version est simple et fait parfaitement le travail. Le seul changement que j'aurais fait serait de retourner le résultat directement, au lieu de le passer en paramètre.
gregschlom
2
La sortie est transmise comme paramètre d'efficacité. Si le résultat était retourné, il faudrait soit une copie du vecteur, soit une allocation de tas qui devrait alors être libérée.
Alec Thomas
2
Un petit ajout à mon commentaire ci-dessus: cette fonction pourrait retourner le vecteur sans pénalité si vous utilisez la sémantique de déplacement C ++ 11.
Alec Thomas
7
@AlecThomas: Même avant C ++ 11, la plupart des compilateurs n'optimiseraient-ils pas la copie de retour via NRVO? (+1 quand même; très succinct)
Marcelo Cantos
11
De toutes les réponses, cela semble être l'une des plus attrayantes et flexibles. Avec la getline avec un délimiteur, bien que ce soit une solution moins évidente. La norme c ++ 11 n'a-t-elle rien pour cela? C ++ 11 prend-il en charge les cartes perforées de nos jours?
Spacen Jasset
124

C'est ma façon préférée de parcourir une chaîne. Vous pouvez faire ce que vous voulez par mot.

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
la source
Est-il possible de déclarer en wordtant que char?
abatishchev
Désolé abatishchev, C ++ n'est pas mon point fort. Mais j'imagine qu'il ne serait pas difficile d'ajouter une boucle intérieure pour parcourir chaque caractère de chaque mot. Mais pour l'instant, je pense que la boucle actuelle dépend des espaces de séparation des mots. À moins que vous ne sachiez qu'il n'y a qu'un seul caractère entre chaque espace, auquel cas vous pouvez simplement transtyper "word" en un caractère ... désolé, je ne peux pas être plus utile, j'ai eu l'intention de
rafraîchir
11
si vous déclarez word comme un caractère, il itérera sur tous les caractères non blancs. C'est assez simple pour essayer:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Ceci est similaire à la question Stack Overflow Comment puis-je tokeniser une chaîne en 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
la source
Cela matérialise-t-il une copie de tous les jetons ou ne conserve-t-il que les positions de début et de fin du jeton actuel?
einpoklum
66

J'aime ce qui suit, car il met les résultats dans un vecteur, prend en charge une chaîne comme délimiteur et donne le contrôle sur la conservation des valeurs vides. Mais ça n'a pas l'air aussi bien alors.

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

Bien sûr, Boost en a un split()qui fonctionne partiellement comme ça. Et, si par `` espace blanc '', vous voulez vraiment dire tout type d'espace blanc, utiliser la division de Boost avec des is_any_of()œuvres géniales.

Shadow2531
la source
Enfin une solution qui gère correctement les jetons vides des deux côtés de la chaîne
fmuecke
53

La STL ne dispose pas déjà d'une telle méthode.

Cependant, vous pouvez soit utiliser la strtok()fonction de C en utilisant le std::string::c_str()membre, soit écrire la vôtre. Voici un exemple de code que j'ai trouvé après une recherche rapide sur Google ( "STL string split" ):

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

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

Si vous avez des questions sur l'exemple de code, laissez un commentaire et je vous expliquerai.

Et ce n'est pas parce qu'il n'implémente pas un typedefitérateur appelé ou une surcharge que l' <<opérateur est un mauvais code. J'utilise les fonctions C assez fréquemment. Par exemple, printfet les scanfdeux sont plus rapides que std::cinet std::cout(de manière significative), lefopen syntaxe est beaucoup plus conviviale pour les types binaires, et ils ont également tendance à produire des EXE plus petits.

Ne soyez pas vendu avec cette offre "Élégance sur performance" .

utilisateur19302
la source
Je connais les fonctions de la chaîne C et je connais aussi les problèmes de performances (que j'ai tous deux notés dans ma question). Cependant, pour cette question spécifique, je recherche une élégante solution C ++.
Ashwin Nanjappa
11
@Nelson LaQuet: Laissez-moi deviner: parce que strtok n'est pas réentrant?
paercebal
40
@Nelson ne passe jamais string.c_str () à strtok! strtok supprime la chaîne d'entrée (insère des caractères '\ 0' pour remplacer chaque délimiteur foudn) et c_str () renvoie une chaîne non modifiable.
Evan Teran
3
@Nelson: Ce tableau doit être de taille str.size () + 1 dans votre dernier commentaire. Mais je suis d'accord avec votre thèse selon laquelle il est idiot d'éviter les fonctions C pour des raisons "esthétiques".
j_random_hacker
2
@paulm: Non, la lenteur des flux C ++ est causée par des facettes. Ils sont toujours plus lents que les fonctions stdio.h même lorsque la synchronisation est désactivée (et sur les chaînes de chaîne, qui ne peuvent pas se synchroniser).
Ben Voigt
42

Voici une fonction partagée qui:

  • est générique
  • utilise C ++ standard (pas de boost)
  • accepte plusieurs délimiteurs
  • ignore les jetons vides (peut facilement être modifié)

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

Exemple d'utilisation:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
la source
Vous avez oublié d'ajouter à la liste d'utilisation: "extrêmement inefficace"
Xander Tulip
1
@XanderTulip, pouvez-vous être plus constructif et expliquer comment ou pourquoi?
Marco M.
3
@XanderTulip: Je suppose que vous faites référence au fait qu'il renvoie le vecteur par valeur. Le Return-Value-Optimization (RVO, google it) devrait s'en occuper. Également en C ++ 11, vous pouvez retourner par référence de déplacement.
Joseph Garvin
3
Cela peut en fait être optimisé davantage: au lieu de .push_back (str.substr (...)), on peut utiliser .emplace_back (str, start, pos - start). De cette façon, l'objet chaîne est construit dans le conteneur et nous évitons ainsi une opération de déplacement + d'autres manigances effectuées par la fonction .substr.
Mihai Bişog
@zoopp oui. Bonne idée. VS10 n'avait pas de support emplace_back lorsque j'ai écrit cela. Je mettrai à jour ma réponse. Merci
Marco M.
36

J'ai une solution en 2 lignes à ce problème:

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;

Ensuite, au lieu d'imprimer, vous pouvez le mettre dans un vecteur.

rhomu
la source
35

Encore un autre moyen flexible et rapide

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

Pour l'utiliser avec un vecteur de chaînes (Edit: Puisque quelqu'un a indiqué de ne pas hériter des 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");

C'est ça! Et ce n'est qu'une façon d'utiliser le tokenizer, comme la façon de compter les mots:

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

Limité par l'imagination;)

Robert
la source
Agréable. Concernant la Appendernote "Pourquoi ne devrions-nous pas hériter d'une classe des classes STL?"
Andreas Spindler
32

Voici une solution simple qui utilise uniquement la bibliothèque regex standard

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

L'argument regex permet de vérifier plusieurs arguments (espaces, virgules, etc.)

Je ne vérifie généralement que pour séparer les espaces et les virgules, j'ai donc également cette fonction par défaut:

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

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

    return Tokenize( str, re );
}

Les "[\\s,]+"vérifications des espaces ( \\s) et des virgules (, ).

Notez que si vous souhaitez diviser wstringau lieu de string,

  • changer tout std::regexenstd::wregex
  • changer tout sregex_token_iteratorenwsregex_token_iterator

Remarque, vous pouvez également vouloir prendre l'argument chaîne par référence, selon votre compilateur.

dk123
la source
Cela aurait été ma réponse préférée, mais std :: regex est cassé dans GCC 4.8. Ils ont dit qu'ils l'avaient correctement mis en œuvre dans GCC 4.9. Je te donne toujours mon +1
mchiasson
1
C'est mon préféré avec des changements mineurs: le vecteur est retourné comme référence comme vous l'avez dit, et les arguments "str" ​​et "regex" sont également passés par des références. THX.
QuantumKarl
1
Les chaînes brutes sont assez utiles pour traiter les modèles d'expression régulière. De cette façon, vous n'avez pas à utiliser les séquences d'échappement ... Vous pouvez simplement utiliser R"([\s,]+)".
Sam
26

Utiliser std::stringstreamcomme vous travaillez parfaitement bien et faites exactement ce que vous vouliez. Si vous cherchez simplement une manière différente de faire les choses, vous pouvez utiliser std::find()/ std::find_first_of()etstd::string::substr() .

Voici un exemple:

#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
la source
Cela ne fonctionne que pour les délimiteurs à caractère unique. Un simple changement permet de travailler avec plusieurs caractères:prev_pos = pos += delimiter.length();
David Doria
25

Si vous aimez utiliser le boost, mais souhaitez utiliser une chaîne entière comme délimiteur (au lieu de caractères uniques comme dans la plupart des solutions proposées précédemment), vous pouvez utiliser le boost_split_iterator.

Exemple de code comprenant un modèle pratique:

#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
la source
20

Voici une solution regex qui utilise uniquement la bibliothèque regex standard. (Je suis un peu rouillé, donc il peut y avoir quelques erreurs de syntaxe, mais c'est au moins l'idée générale)

#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
la source
Réponses similaires avec une approche regex peut-être meilleure: ici et ici .
nobar
20

Il y a une fonction nommée 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
la source
3
strtokest de la bibliothèque standard C, pas C ++. Il n'est pas sûr de l'utiliser dans des programmes multithread. Il modifie la chaîne d'entrée.
Kevin Panko
13
Parce qu'il stocke le pointeur char du premier appel dans une variable statique, de sorte que lors des appels suivants lorsque NULL est passé, il se souvient du pointeur à utiliser. Si un deuxième thread appelle strtokalors qu'un autre thread est encore en cours de traitement, ce pointeur de caractère sera remplacé et les deux threads auront alors des résultats incorrects. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
comme mentionné précédemment, strtok n'est pas sûr et même en C, strtok_r est recommandé pour une utilisation
systemsfault
4
strtok_r peut être utilisé si vous êtes dans une section de code accessible. c'est la seule solution de tout ce qui précède qui n'est pas du «bruit de ligne», et témoigne de ce qui est exactement faux avec c ++
Erik Aronesty
Mis à jour afin qu'il ne puisse pas y avoir d'objection pour des raisons de sécurité des threads des winks C ++.
Erik Aronesty
17

Le flux de chaînes peut être pratique si vous devez analyser la chaîne par des symboles non spatiaux:

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
la source
14

Jusqu'à présent, j'ai utilisé celui de Boost , mais j'avais besoin de quelque chose qui n'en dépendait pas, alors je suis arrivé à ceci:

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

Un bon point est qu'en separatorsvous pouvez passer plus d'un caractère.

Goran
la source
13

J'ai roulé le mien en utilisant strtok et utilisé boost pour diviser une chaîne. La meilleure méthode que j'ai trouvée est la bibliothèque C ++ String Toolkit . Il est incroyablement flexible et rapide.

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

La boîte à outils a beaucoup plus de flexibilité que cet exemple simple ne le montre, mais son utilité pour analyser une chaîne en éléments utiles est incroyable.

DannyK
la source
13

Court et élégant

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

peut utiliser n'importe quelle chaîne comme délimiteur, peut également être utilisé avec des données binaires (std :: string prend en charge les données binaires, y compris les valeurs nulles)

en utilisant:

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

production:

this
is
!example!string
1438233
la source
1
J'aime cette solution car elle permet au séparateur d'être une chaîne et non un caractère, cependant, il modifie en place la chaîne, donc il force la création d'une copie de la chaîne d'origine.
Alessandro Teruzzi du
11

J'ai fait cela parce que j'avais besoin d'un moyen facile de diviser les chaînes et les chaînes basées sur c ... J'espère que quelqu'un d'autre peut également le trouver utile. De plus, il ne repose pas sur des jetons et vous pouvez utiliser des champs comme délimiteurs, ce qui est une autre clé dont j'avais besoin.

Je suis sûr que des améliorations peuvent être apportées pour améliorer encore son élégance et veuillez le faire par tous les moyens

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

Exemples:

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

Sortira:

Ceci
est
un
exemple de
chaîne

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

Pour conserver les entrées vides (par défaut les vides seront exclus):

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

Le but était de la rendre similaire à la méthode Split () de C # où le fractionnement d'une chaîne est aussi simple que:

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

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

J'espère que quelqu'un d'autre pourra trouver cela aussi utile que moi.

Steve Dell
la source
10

Et ça:

#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
la source
C'est la meilleure réponse ici, si vous ne souhaitez scinder qu'un seul caractère de délimitation. La question d'origine voulait cependant se diviser en espaces blancs, c'est-à-dire toute combinaison d'un ou plusieurs espaces ou tabulations consécutifs. Vous avez effectivement répondu stackoverflow.com/questions/53849
Oktalist
10

Cette réponse prend la chaîne et la place dans un vecteur de chaînes. Il utilise la bibliothèque boost.

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

Voici une autre façon de le faire ..

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);
  }
}
utilisateur246110
la source
9

J'aime utiliser les méthodes boost / regex pour cette tâche car elles offrent une flexibilité maximale pour spécifier les critères de fractionnement.

#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
la source
9

Récemment, j'ai dû diviser un mot en forme de chameau en sous-mots. Il n'y a pas de délimiteurs, juste des caractères supérieurs.

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

Par exemple, cela divise "AQueryTrades" en "A", "Query" et "Trades". La fonction fonctionne avec des cordes étroites et larges. Parce qu'il respecte les paramètres régionaux actuels, il divise "RaumfahrtÜberwachungsVerordnung" en "Raumfahrt", "Überwachungs" et "Verordnung".

La note std::upperdoit être vraiment passée comme argument de modèle de fonction. Ensuite, le plus généralisé de cette fonction peut se diviser en délimiteurs comme ",", ";"ou " "trop.

Andreas Spindler
la source
2
Il y a eu 2 tours. C'est zonte. On dirait que mon anglais a dû beaucoup d'un «allemand». Cependant, le révisionniste n'a pas corrigé deux bugs mineurs peut-être parce qu'ils étaient évidents de toute façon: std::isupperpouvaient être passés en argument, non std::upper. Deuxième mettre un typenameavant le 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
la source
9

Utilisation std::string_viewet range-v3bibliothèque d' 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";}
    );
}

En utilisant une forboucle de plage au lieu d'un ranges::for_eachalgorithme:

#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
la source
Yepp, la gamme de base semble meilleure - je suis d'accord
Porsche9II