Question La manière la plus élégante de répéter les mots d'une chaîne [fermé]


Quelle est la manière la plus élégante d'itérer les mots d'une chaîne? La chaîne peut être supposée être 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 par ce type de manipulation / accès de caractères. Aussi, s'il vous plaît donner la priorité à l'élégance sur l'efficacité de votre réponse.

La meilleure solution que j'ai maintenant 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);
}

2638


origine


Réponses:


Pour ce que ça vaut, voici une autre façon d'extraire des jetons à partir d'une chaîne d'entrée, en ne comptant que sur les fonctionnalités de la 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 générique copy algorithme.

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

... ou créez le vector directement:

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

1188



J'utilise ceci pour séparer une 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::stringstream ss(s);
    std::string item;
    while (std::getline(ss, 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 un est vide:

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

2307



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 approche. Et puisqu'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 plus de détails.


794



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

321



Pour ceux avec qui il ne convient pas 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 bon endroit (et je pense que la classe de conteneur modèle est une addition awesomely é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> types comme second paramètre (ContainerT)... mais list<> est beaucoup plus rapide que vector<> pour quand 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> où subString ne fait pas de copies pour des augmentations de vitesse incroyables.

C'est plus du double de la vitesse la plus rapide sur cette page et presque 5 fois plus rapide que d'autres. En outre, avec les types de paramètres parfaits, vous pouvez éliminer toutes les copies de chaînes et de listes pour augmenter la vitesse.

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

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

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


168



Voici une autre solution. C'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 manipuler des séparateurs de cordes, des cordes larges, etc.

Notez que le fractionnement "" résulte en une seule chaîne vide et 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 vous souhaitez diviser une chaîne à plusieurs délimiteurs en ignorant les jetons vides, 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;
}

150



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

106



Ceci est similaire à la question Stack Overflow Comment puis-je marquer 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;
    }
}

76



J'aime ce qui suit car il met les résultats dans un vecteur, prend en charge une chaîne comme un delim et donne le contrôle sur la conservation des valeurs vides. Mais, il ne semble pas aussi bon 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 a un split() cela fonctionne partiellement comme ça. Et, si par «espace blanc», vous voulez vraiment dire n'importe quel type d'espace blanc, en utilisant la scission de Boost avec is_any_of() fonctionne très bien.


65