2011-03-28 3 views
2

J'ai donc la chaîne de données suivante, qui est reçue via une connexion TCP winsock, et je voudrais faire une tokenisation avancée, dans un vecteur de structures, où chaque structure représente un enregistrement.tokenizing une chaîne de données dans un vecteur de structures?

std::string buf = "44:william:adama:commander:stuff\n33:luara:roslin:president:data\n" 

struct table_t 
{ 
    std::string key; 
    std::string first; 
    std::string last; 
    std::string rank; 
    std::additional; 
}; 

Chaque enregistrement de la chaîne est délimité par un retour chariot. Ma tentative de diviser les enregistrements, mais pas encore diviser les champs:

void tokenize(std::string& str, std::vector<string>records) 
{ 
    // Skip delimiters at beginning. 
    std::string::size_type lastPos = str.find_first_not_of("\n", 0); 
    // Find first "non-delimiter". 
    std::string::size_type pos  = str.find_first_of("\n", lastPos); 
    while (std::string::npos != pos || std::string::npos != lastPos) 
    { 
     // Found a token, add it to the vector. 
     records.push_back(str.substr(lastPos, pos - lastPos)); 
     // Skip delimiters. Note the "not_of" 
     lastPos = str.find_first_not_of("\n", pos); 
     // Find next "non-delimiter" 
     pos = str.find_first_of("\n", lastPos); 
    } 
} 

Il semble tout à fait inutile de répéter tout ce code à nouveau pour tokenizer plus chaque enregistrement par l'intermédiaire du côlon (séparateur de champ interne) dans le struct et pousser chaque structure dans un vecteur. Je suis sûr qu'il y a une meilleure façon de faire cela, ou peut-être que le design est en soi erroné.

Nous vous remercions de votre aide.

+1

Si vous pouvez utiliser Boost, ce serait plutôt bien fait avec sa bibliothèque de tokenizer, sa bibliothèque d'algorithmes de chaînes, ou, pour la solution la plus robuste, avec boost.spirit, comme ici: http://www.boost.org/doc/libs/1_46_1/libs/spirit/doc /html/spirit/qi/tutorials/employee___parsing_into_structs.html – Cubbi

+0

raté ce commentaire. +1 pour l'esprit altho qui est trop lourd pour le format de données utilisé dans ce cas – user237419

+0

utilisez [boost :: tokenizer] (http://www.boost.org/doc/libs/1_46_1/libs/tokenizer/index.html) – user237419

Répondre

1

Pour briser la chaîne en enregistrements, j'utiliserais istringstream, si seulement parce que cela va simplifier les changements plus tard quand je veux lire un fichier. Pour tokenizing, la solution la plus évidente est boost :: regex, donc:

std::vector<table_t> parse(std::istream& input) 
{ 
    std::vector<table_t> retval; 
    std::string line; 
    while (std::getline(input, line)) { 
     static boost::regex const pattern(
      "\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\)"); 
     boost::smatch matched; 
     if (!regex_match(line, matched, pattern)) { 
      // Error handling... 
     } else { 
      retval.push_back(
       table_t(matched[1], matched[2], matched[3], 
         matched[4], matched[5])); 
     } 
    } 
    return retval; 
} 

(je l'ai supposé le constructeur logique table_t aussi:. Il y a une très longue tradition en C que les noms se terminant par _t sont typedef de , alors vous êtes probablement mieux de trouver une autre convention)

+0

vous devriez pinguer Siek et lui dire :: tokenizer est inutile car avec regex vous pouvez faire n'importe quoi. évidemment – user237419

+0

@adirau Il a demandé comment éviter la duplication. L'utilisation d'un outil existant est la solution évidente. Dans ce cas, c'est aussi clairement la solution la plus simple (au moins si vous voulez vérifier les erreurs). –

+0

éviter la duplication par la réutilisation de code; ne peut pas dire que si vous utilisez getline comme premier tokenizer et regex comme second tokenizer vous évitez la duplication;) pas le plus simple, pas évident et même si vous voulez vérifier les erreurs; cette expression régulière acceptera les erreurs au niveau du jeton; s'il a besoin d'une vérification d'erreur, peut-être que: spirit est une meilleure solution, comme l'a mentionné Cubbi dans le premier commentaire – user237419

2

Ma solution:.

struct colon_separated_only: std::ctype<char> 
{ 
    colon_separated_only(): std::ctype<char>(get_table()) {} 

    static std::ctype_base::mask const* get_table() 
    { 
     typedef std::ctype<char> cctype; 
     static const cctype::mask *const_rc= cctype::classic_table(); 

     static cctype::mask rc[cctype::table_size]; 
     std::memcpy(rc, const_rc, cctype::table_size * sizeof(cctype::mask)); 

     rc[':'] = std::ctype_base::space; 
     return &rc[0]; 
    } 
}; 

struct table_t 
{ 
    std::string key; 
    std::string first; 
    std::string last; 
    std::string rank; 
    std::string additional; 
}; 

int main() { 
     std::string buf = "44:william:adama:commander:stuff\n33:luara:roslin:president:data\n"; 
     stringstream s(buf); 
     s.imbue(std::locale(std::locale(), new colon_separated_only())); 
     table_t t; 
     std::vector<table_t> data; 
     while (s >> t.key >> t.first >> t.last >> t.rank >> t.additional) 
     { 
      data.push_back(t); 
     } 
     for(size_t i = 0 ; i < data.size() ; ++i) 
     { 
      cout << data[i].key <<" "; 
      cout << data[i].first <<" "<<data[i].last <<" "; 
      cout << data[i].rank <<" "<< data[i].additional << endl; 
     } 
     return 0; 
} 

sortie:

44 william adama commander stuff 
33 luara roslin president data 

Démo en ligne: http://ideone.com/JwZuk


La technique que j'utilisé ici est décrit dans mon autre solution à la question différente:

Elegant ways to count the frequency of words in a file

+0

Je dois encore vérifier ctype, devra lire à ce sujet. Merci de votre aide. – rem45acp