web-dev-qa-db-fra.com

Puis-je utiliser deux délimiteurs ou plus dans la fonction getline de C++?

Je voudrais savoir comment puis-je utiliser 2 délimiteurs ou plus dans la fonction getline, c'est mon problème: 

Le programme lit un fichier texte ... chaque ligne va ressembler à:

   New Your, Paris, 100
   CityA, CityB, 200

J'utilise getline (fichier, ligne), mais j'ai la ligne complète lorsque je veux obtenir CityA, puis CityB, puis le numéro; et si j'utilise ',' délimiteur, je ne saurai pas quelle est la prochaine ligne, alors j'essaie de trouver une solution ..

Cependant, comment pourrais-je utiliser la virgule et\n comme délimiteur?

quelques rayures:

string line;
ifstream file("text.txt");
if(file.is_open())
   while(!file.eof()){
     getline(file, line);
        // here I need to get each string before comma and \n
   }
7
user6185425

Vous pouvez lire une ligne en utilisant std::getline, puis la transmettre à std::stringstream et lire les valeurs séparées par des virgules

string line;
ifstream file("text.txt");
if(file.is_open()){
   while(getline(file, line)){   // get a whole line
       std::stringstream ss(line);
        while(getline(ss, line, ',')){
             // You now have separate entites here
        }
   }
8
WhiZTiM

Non, std::getline () n'accepte qu'un seul caractère pour remplacer le délimiteur par défaut. std::getline() n'a pas d'option pour plusieurs délimiteurs alternatifs.

La bonne façon d’analyser ce type d’entrée est d’utiliser le std::getline () par défaut pour lire toute la ligne dans un std::string, puis construire un std::istringstream, puis l’analyser plus avant, en valeurs séparées par des virgules.

Toutefois, si vous analysez vraiment les valeurs séparées par des virgules, vous devez utiliser un analyseur CSV approprié .

4
Sam Varshavchik

Il est souvent plus intuitif et efficace d’analyser la saisie de caractères de manière hiérarchique et arborescente, en commençant par scinder la chaîne en blocs principaux, puis en traitant chacun des blocs en les séparant en parties plus petites. etc.

Une alternative à cela est de créer un jeton comme le fait strtok - du début de l'entrée, en traitant un jeton à la fois jusqu'à la fin de l'entrée. Ceci peut être préféré lors de l'analyse d'entrées simples, car il est facile à implémenter. Ce style peut également être utilisé lors de l'analyse d'entrées avec une structure imbriquée, mais cela nécessite de conserver certaines informations de contexte, qui pourraient devenir trop complexes pour être gérées dans une seule fonction ou une région limitée de code.

Quelqu'un qui s'appuie sur la bibliothèque std C++ finit généralement par utiliser un std::stringstream, ainsi que std::getline pour tokenize une chaîne d'entrée. Mais cela ne vous donne qu'un seul délimiteur. Ils n’envisageraient jamais d’utiliser strtok, car c’est une pièce indésirable non réentrante de la bibliothèque d’exécution C. Ainsi, ils finissent par utiliser des flux, et avec un seul délimiteur, on est obligé d'utiliser un style d'analyse hiérarchique.

Mais zneak a soulevé std::string::find_first_of, qui prend un ensemble de caractères et renvoie la position la plus proche du début de la chaîne contenant un caractère de cet ensemble. Et il existe d'autres fonctions membres: find_last_of, find_first_not_of et plus, qui semblent exister uniquement dans le but d'analyser des chaînes. Mais std::string ne parvient pas à fournir des fonctions de tokenizing utiles.

Une autre option est la bibliothèque <regex>, qui peut faire tout ce que vous voulez, mais elle est nouvelle et vous devrez vous habituer à sa syntaxe.

Mais, avec très peu d'effort, vous pouvez exploiter les fonctions existantes dans std::string pour effectuer des tâches de tokenizing, sans recourir à des flux. Voici un exemple simple. get_to() est la fonction tokenizing et tokenize montre comment elle est utilisée.

Le code de cet exemple sera plus lent que strtok, car il efface en permanence les caractères du début de la chaîne en cours d'analyse, et copie et renvoie également des sous-chaînes. Cela rend le code facile à comprendre, mais cela ne signifie pas pour autant que la création de jetons soit plus efficace est impossible. Cela ne serait même pas beaucoup plus compliqué que cela: vous garderiez simplement une trace de votre position actuelle, utilisez ceci comme argument start dans les fonctions membres std::string et ne modifiez jamais la chaîne source. Et même de meilleures techniques existent, sans aucun doute.

Pour comprendre le code de l'exemple, commencez par le bas, où main() est et où vous pouvez voir comment les fonctions sont utilisées. Le sommet de ce code est dominé par les fonctions utilitaires de base et les commentaires stupides.

#include <iostream>
#include <string>
#include <utility>

namespace string_parsing {
// in-place trim whitespace off ends of a std::string
inline void trim(std::string &str) {
    auto space_is_it = [] (char c) {
        // A few asks:
        // * Suppress criticism WRT localization concerns
        // * Avoid jumping to conclusions! And seeing monsters everywhere! 
        //   Things like...ah! Believing "thoughts" that assumptions were made
        //   regarding character encoding.
        // * If an obvious, portable alternative exists within the C++ Standard Library,
        //   you will see it in 2.0, so no new defect tickets, please.
        // * Go ahead and ignore the rumor that using lambdas just to get 
        //   local function definitions is "cheap" or "dumb" or "ignorant."
        //   That's the latest round of FUD from...*mumble*.
        return c > '\0' && c <= ' '; 
    };

    for(auto rit = str.rbegin(); rit != str.rend(); ++rit) {
        if(!space_is_it(*rit)) {
            if(rit != str.rbegin()) {
                str.erase(&*rit - &*str.begin() + 1);
            }
            for(auto fit=str.begin(); fit != str.end(); ++fit) {
                if(!space_is_it(*fit)) {
                    if(fit != str.begin()) {
                        str.erase(str.begin(), fit);
                    }
                    return;
    }   }   }   }
    str.clear();
}

// get_to(string, <delimiter set> [, delimiter])
// The input+output argument "string" is searched for the first occurance of one 
// from a set of delimiters.  All characters to the left of, and the delimiter itself
// are deleted in-place, and the substring which was to the left of the delimiter is
// returned, with whitespace trimmed.
// <delimiter set> is forwarded to std::string::find_first_of, so its type may match
// whatever this function's overloads accept, but this is usually expressed
// as a string literal: ", \n" matches commas, spaces and linefeeds.
// The optional output argument "found_delimiter" receives the delimiter character just found.
template <typename D>
inline std::string get_to(std::string& str, D&& delimiters, char& found_delimiter) {
    const auto pos = str.find_first_of(std::forward<D>(delimiters));
    if(pos == std::string::npos) {
        // When none of the delimiters are present,
        // clear the string and return its last value.
        // This effectively makes the end of a string an
        // implied delimiter.
        // This behavior is convenient for parsers which
        // consume chunks of a string, looping until
        // the string is empty.
        // Without this feature, it would be possible to 
        // continue looping forever, when an iteration 
        // leaves the string unchanged, usually caused by
        // a syntax error in the source string.
        // So the implied end-of-string delimiter takes
        // away the caller's burden of anticipating and 
        // handling the range of possible errors.
        found_delimiter = '\0';
        std::string result;
        std::swap(result, str);
        trim(result);
        return result;
    }
    found_delimiter = str[pos];
    auto left = str.substr(0, pos);
    trim(left);
    str.erase(0, pos + 1);
    return left;
}

template <typename D>
inline std::string get_to(std::string& str, D&& delimiters) {
    char discarded_delimiter;
    return get_to(str, std::forward<D>(delimiters), discarded_delimiter);
}

inline std::string pad_right(const std::string&     str,
                             std::string::size_type min_length,
                             char                   pad_char=' ')
{
    if(str.length() >= min_length ) return str;
    return str + std::string(min_length - str.length(), pad_char);
}

inline void tokenize(std::string source) {
    std::cout << source << "\n\n";
    bool quote_opened = false;
    while(!source.empty()) {
        // If we just encountered an open-quote, only include the quote character
        // in the delimiter set, so that a quoted token may contain any of the
        // other delimiters.
        const char* delimiter_set = quote_opened ? "'" : ",'{}";
        char delimiter;
        auto token = get_to(source, delimiter_set, delimiter);
        quote_opened = delimiter == '\'' && !quote_opened;
        std::cout << "    " << pad_right('[' + token + ']', 16) 
            << "   " << delimiter << '\n';
    }
    std::cout << '\n';
}
}

int main() {
    string_parsing::tokenize("{1.5, null, 88, 'hi, {there}!'}");
}

Cela génère:

{1.5, null, 88, 'hi, {there}!'}

    []                 {
    [1.5]              ,
    [null]             ,
    [88]               ,
    []                 '
    [hi, {there}!]     '
    []                 }
2
Christopher Oicles

Je ne pense pas que ce soit comme ça que vous devriez attaquer le problème (même si vous pouviez le faire); au lieu:

  1. Utilisez ce que vous devez lire dans chaque ligne
  2. Ensuite, séparez cette ligne par des virgules pour obtenir les éléments souhaités.

Si strtok fera le travail pour # 2, vous pouvez toujours convertir votre chaîne en tableau de caractères.

1
Scott Hunter