2010-06-15 5 views
0

Pour votre information: pas de boost, oui il a cela, je veux réinventer la roue;)iterator sélective

Y at-il une certaine forme d'un iterator sélectif (possible) en C++? Ce que je veux est de séparer les chaînes comme ceci:

some:word{or other 

à une forme comme ceci:

some : word { or other 

Je peux le faire avec deux boucles et find_first_of (« : ») et (« { ») mais cela me semble (très) inefficace. Je pensais qu'il y aurait peut-être un moyen de créer/définir/écrire un itérateur qui parcourrait toutes ces valeurs avec for_each. Je crains que cela m'oblige à écrire une classe d'itérateur à part entière et trop complexe pour une chaîne std :: string.

Je pensais que ce serait faire:

std::vector<size_t> list; 
size_t index = mystring.find(":"); 
while(index != std::string::npos) 
{ 
    list.push_back(index); 
    index = mystring.find(":", list.back()); 
} 
std::for_each(list.begin(), list.end(), addSpaces(mystring)); 

Cela semble désordre pour moi, et je suis tout à fait sûr une façon plus élégante de le faire existe. Mais je ne peux pas y penser. Quelqu'un a une idée brillante? Merci

PS: Je n'ai pas testé le code affiché, juste une écriture-up rapide de ce que je voudrais essayer

MISE À JOUR: après avoir pris toutes vos réponses en compte, je suis venu avec cela, et il travaille à mon goût :). cela suppose que le dernier char est un retour à la ligne ou quelque chose, sinon une fin {, } ou : ne sera pas traitée.

void tokenize(string &line) 
{ 
    char oneBack = ' '; 
    char twoBack = ' '; 
    char current = ' '; 
    size_t length = line.size(); 

    for(size_t index = 0; index<length; ++index) 
    { 
     twoBack = oneBack; 
     oneBack = current; 
     current = line.at(index); 
     if(isSpecial(oneBack)) 
     { 
      if(!isspace(twoBack)) // insert before 
      { 
       line.insert(index-1, " "); 
       ++index; 
       ++length; 
      } 
      if(!isspace(current)) // insert after 
      { 
       line.insert(index, " "); 
       ++index; 
       ++length; 
      } 
     } 
    } 

commentaires sont les bienvenus comme toujours :)

+3

"Existe-t-il une forme d'itérateur sélectif (possible) en C++?" Eh bien, selon vous, Boost a ceci. Peut-être que je suis pédant, mais si vous demandez si quelque chose est possible immédiatement après avoir cité un exemple de ce qui est possible, je vais penser que votre question est un peu bête. Vous pourriez trouver instructif de lire le code source de l'implémentation de Boost pour comprendre comment ils l'ont fait. Même si vous voulez le réinventer, je suis sûr que Boost offrira quelques conseils sur la façon de le faire correctement. – Brian

+1

* "Je crains que cela ne m'oblige à écrire une classe d'itérateur à part entière et trop complexe" * ... Boost a ses utilitaires d'itérateur exactement parce que l'écriture de ses propres itérateurs est agaçante. –

Répondre

1
std::string const str = "some:word{or other"; 

std::string result; 
result.reserve(str.size()); 
for (std::string::const_iterator it = str.begin(), end = str.end(); 
    it != end; ++it) 
{ 
    if (isalnum(*it)) 
    { 
    result.push_back(*it); 
    } 
    else 
    { 
    result.push_back(' '); result.push_back(*it); result.push_back(' '); 
    } 
} 

Version Insert pour accélérer

std::string str = "some:word{or other"; 

for (std::string::iterator it = str.begin(), end = str.end(); it != end; ++it) 
{ 
    if (!isalnum(*it)) 
    { 
    it = str.insert(it, ' ') + 2; 
    it = str.insert(it, ' '); 
    end = str.end(); 
    } 
} 

Notez que std::string::insert inserts AVANT l'itérateur passé et retourne un itérateur au caractère nouvellement inséré. L'affectation est importante car la mémoire tampon peut avoir été réaffectée à un autre emplacement mémoire (les itérateurs sont invalidés par l'insertion). Notez également que vous ne pouvez pas conserver end pour toute la boucle, chaque fois que vous insérez, vous devez le recalculer.

+0

Vous m'avez donné la réponse la plus claire, je pense. Peut-être pas le meilleur, mais j'aime ça. Comment serait ma solution vs la vôtre en termes de vitesse (j'utilise insert, mais vous avez une copie de la chaîne). Merci – rubenvb

+0

Vous pouvez utiliser insert avec le mien aussi, les itérateurs sont assez flexibles, c'est juste que pour la visualisation des algorithmes, les copies sont généralement plus faciles à commencer avec :) Je vais modifier pour ajouter la version d'insertion. –

0

Que diriez-vous quelque chose comme:

std::string::const_iterator it, end = mystring.end(); 
for(it = mystring.begin(); it != end; ++it) { 
    if (!isalnum(*it)) 
    list.push_back(it); 
} 

De cette façon, vous n'itérer une fois par la chaîne, et isalnum de semble ctype.h faire ce que tu veux. Bien sûr, le code ci-dessus est très simpliste et incomplet et ne propose qu'une solution.

0

une manière plus élégante de le faire existe.

Je ne sais pas comment BOOST met en œuvre, mais de façon traditionnelle est en alimentant le caractère de chaîne d'entrée par caractère dans un FSM qui détecte où des jetons (mots, symboles) commencent et se terminent.

Je peux le faire avec deux boucles et find_first_of (":") et ("{")

Une boucle avec std :: find_first_of() devrait suffire.

Bien que je suis toujours un grand fan des FSM pour de telles tâches d'analyse.

P.S. Similar question

0

Vous cherchez à marquer la chaîne d'entrée, ala strtok?

Si oui, voici une fonction de segmentation que vous pouvez utiliser.Il faut une entrée string et une chaîne de délimiteurs (chaque caractère dans la chaîne est un délimiteur possible), et il retourne un vecteur de token s. Chaque token est un tuple avec la chaîne delimitted, et le séparateur utilisé dans ce cas:

#include <cstdlib> 
#include <vector> 
#include <string> 
#include <functional> 
#include <iostream> 
#include <algorithm> 
using namespace std; 

// FUNCTION :  stringtok(char const* Raw, string sToks) 
// PARAMATERS : Raw  Pointer to NULL-Terminated string containing a string to be tokenized. 
//     sToks string of individual token characters -- each character in the string is a token 
// DESCRIPTION : Tokenizes a string, much in the same was as strtok does. The input string is not modified. The 
//     function is called once to tokenize a string, and all the tokens are retuned at once. 
// RETURNS :  Returns a vector of strings. Each element in the vector is one token. The token character is 
//     not included in the string. The number of elements in the vector is N+1, where N is the number 
//     of times the Token character is found in the string. If one token is an empty string (as with the 
//     string "string1##string3", where the token character is '#'), then that element in the vector 
//     is an empty string. 
// NOTES :   
// 
typedef pair<char,string> token; // first = delimiter, second = data 
inline vector<token> tokenize(const string& str, const string& delims, bool bCaseSensitive=false) // tokenizes a string, returns a vector of tokens 
{ 
    bCaseSensitive; 

    // prologue 
    vector<token> vRet; 
    // tokenize input string 
    for(string::const_iterator itA = str.begin(), it=itA; it != str.end(); it = find_first_of(++it,str.end(),delims.begin(),delims.end())) 
    { 
     // prologue 
     // find end of token 
     string::const_iterator itEnd = find_first_of(it+1,str.end(),delims.begin(),delims.end()); 
     // add string to output 
     if(it == itA) vRet.push_back(make_pair(0,string(it,itEnd))); 
     else   vRet.push_back(make_pair(*it,string(it+1,itEnd))); 
     // epilogue 
    } 
    // epilogue 
    return vRet; 
} 

using namespace std; 

int main() 
{ 
    string input = "some:word{or other"; 
    typedef vector<token> tokens; 
    tokens toks = tokenize(input.c_str(), " :{"); 
    cout << "Input: '" << input << " # Tokens: " << toks.size() << "'\n"; 
    for(tokens::iterator it = toks.begin(); it != toks.end(); ++it) 
    { 
     cout << " Token : '" << it->second << "', Delimiter: '" << it->first << "'\n"; 
    } 
    return 0; 

} 
4

qui est relativement facile en utilisant le std :: istream_iterator.

Ce que vous devez faire est de définir votre propre classe (disons Term). Puis définissez comment lire un seul "mot" (terme) du flux en utilisant l'opérateur >>.

Je ne connais pas votre définition exacte d'un mot est, donc je suis en utilisant la définition suivante:

  • Toute séquence consécutive de caractères alpha numériques est un terme
  • Tout caractère espace blanc simple non ce n'est pas non plus alpha-numérique est un mot.

Essayez ceci:

#include <string> 
#include <sstream> 
#include <iostream> 
#include <iterator> 
#include <algorithm> 

class Term 
{ 
    public: 

     // This cast operator is not required but makes it easy to use 
     // a Term anywhere that a string can normally be used. 
     operator std::string const&() const {return value;} 

    private: 
     // A term is just a string 
     // And we friend the operator >> to make sure we can read it. 
     friend std::istream& operator>>(std::istream& inStr,Term& dst); 
     std::string  value; 
}; 

Maintenant, tout ce que nous devons faire est de définir un opérateur >> qui lit un mot selon les règles:

// This function could be a lot neater using some boost regular expressions. 
// I just do it manually to show it can be done without boost (as requested) 
std::istream& operator>>(std::istream& inStr,Term& dst) 
{ 
    // Note the >> operator drops all proceeding white space. 
    // So we get the first non white space 
    char first; 
    inStr >> first; 

    // If the stream is in any bad state the stop processing. 
    if (inStr) 
    { 
     if(std::isalnum(first)) 
     { 
      // Alpha Numeric so read a sequence of characters 
      dst.value = first; 

      // This is ugly. And needs re-factoring. 
      while((first = insStr.get(), inStr) && std::isalnum(first)) 
      { 
       dst.value += first; 
      } 

      // Take into account the special case of EOF. 
      // And bad stream states. 
      if (!inStr) 
      { 
       if (!inStr.eof()) 
       { 
        // The last letter read was not EOF and and not part of the word 
        // So put it back for use by the next call to read from the stream. 
        inStr.putback(first); 
       } 
       // We know that we have a word so clear any errors to make sure it 
       // is used. Let the next attempt to read a word (term) fail at the outer if. 
       inStr.clear(); 
      } 
     } 
     else 
     { 
      // It was not alpha numeric so it is a one character word. 
      dst.value = first; 
     } 
    } 
    return inStr; 
} 

Alors maintenant, nous pouvons l'utiliser dans des algorithmes standard en utilisant simplement l'istream_iterator

int main() 
{ 
    std::string   data = "some:word{or other"; 
    std::stringstream dataStream(data); 


    std::copy( // Read the stream one Term at a time. 
       std::istream_iterator<Term>(dataStream), 
       std::istream_iterator<Term>(), 

       // Note the ostream_iterator is using a std::string 
       // This works because a Term can be converted into a string. 
       std::ostream_iterator<std::string>(std::cout, "\n") 
      ); 

} 

La sortie:

> ./a.exe 
some 
: 
word 
{ 
or 
other