web-dev-qa-db-fra.com

Fractionner une chaîne par un caractère

Je sais que c'est un problème assez facile mais je veux juste le résoudre une fois pour toutes

Je voudrais simplement diviser une chaîne en un tableau en utilisant un caractère comme délimiteur divisé. (Tout comme le célèbre C # .Divisé() une fonction. Je peux bien sûr appliquer l'approche par force brute mais je me demande s'il n'y a rien de mieux que cela.

Jusqu'à présent, j'ai cherché et probablement le le plus proche approche de solution est l'utilisation de strtok (), cependant en raison de son inconvénient (conversion de votre chaîne en un tableau de caractères, etc.), je n'aime pas l'utiliser. Existe-t-il un moyen plus simple de mettre cela en œuvre?

Remarque: Je voulais souligner cela parce que les gens pourraient demander "Comment se fait-il que la force brute ne fonctionne pas". Ma solution de force brute consistait à créer une boucle et à utiliser le substr () fonction à l'intérieur. Cependant, comme il nécessite point de départ et la longueur, ça échoue quand je veux diviser une date. Parce que l'utilisateur peut le saisir comme 7/12/2012 ou 07/3/2011, où je peux vraiment dire la longueur avant de calculer l'emplacement suivant du délimiteur '/'.

37
Ali

Utilisation de vecteurs, de chaînes et de chaînes de chaînes. Un peu lourd mais ça fait l'affaire.

std::stringstream test("this_is_a_test_string");
std::string segment;
std::vector<std::string> seglist;

while(std::getline(test, segment, '_'))
{
   seglist.Push_back(segment);
}

Ce qui donne un vecteur avec le même contenu que

std::vector<std::string> seglist{ "this", "is", "a", "test", "string" };
76
thelazydeveloper

Une autre façon (C++ 11/boost) pour les personnes qui aiment RegEx. Personnellement, je suis un grand fan de RegEx pour ce type de données. L'OMI est beaucoup plus puissant que le simple fractionnement de chaînes à l'aide d'un délimiteur, car vous pouvez choisir d'être beaucoup plus intelligent sur ce qui constitue des données "valides" si vous le souhaitez.

#include <string>
#include <algorithm>    // copy
#include <iterator>     // back_inserter
#include <regex>        // regex, sregex_token_iterator
#include <vector>

int main()
{
    std::string str = "08/04/2012";
    std::vector<std::string> tokens;
    std::regex re("\\d+");

    //start/end points of tokens in str
    std::sregex_token_iterator
        begin(str.begin(), str.end(), re),
        end;

    std::copy(begin, end, std::back_inserter(tokens));
}
13
Ben Cottrell

Boost a le split () que vous recherchez dans algorithm/string.hpp:

std::string sample = "07/3/2011";
std::vector<string> strs;
boost::split(strs, sample, boost::is_any_of("/"));
10
chrisaycock

Une autre possibilité consiste à imprégner un flux d'un environnement local qui utilise une facette spéciale ctype. Un flux utilise la facette ctype pour déterminer ce qui est un "espace blanc", qu'il traite comme des séparateurs. Avec une facette ctype qui classe votre caractère séparateur en tant qu'espace, la lecture peut être assez triviale. Voici une façon d'implémenter la facette:

struct field_reader: std::ctype<char> {

    field_reader(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(table_size, std::ctype_base::mask());

        // we'll assume dates are either a/b/c or a-b-c:
        rc['/'] = std::ctype_base::space;
        rc['-'] = std::ctype_base::space;
        return &rc[0];
    }
};

Nous utilisons cela en utilisant imbue pour dire à un flux d'utiliser un environnement local qui l'inclut, puis lisons les données de ce flux:

std::istringstream in("07/3/2011");
in.imbue(std::locale(std::locale(), new field_reader);

Avec cela en place, le fractionnement devient presque trivial - il suffit d'initialiser un vecteur en utilisant un couple de istream_iterators pour lire les morceaux de la chaîne (qui est incorporé dans le istringstream):

std::vector<std::string>((std::istream_iterator<std::string>(in),
                          std::istream_iterator<std::string>());

Évidemment, cela a tendance à être excessif si vous ne l'utilisez qu'en un seul endroit. Si vous l'utilisez beaucoup, cependant, cela peut contribuer grandement à garder le reste du code assez propre.

4
Jerry Coffin

Jetez un oeil à boost :: tokenizer

Si vous souhaitez utiliser votre propre méthode, vous pouvez utiliser std::string::find() pour déterminer les points de division.

2
Rafał Rawicki

Je n'aime pas intrinsèquement stringstream, mais je ne sais pas pourquoi. Aujourd'hui, j'ai écrit cette fonction pour permettre de diviser un std::string par n'importe quel caractère ou chaîne arbitraire dans un vecteur. Je sais que cette question est ancienne, mais je voulais partager une autre façon de diviser std::string.

Ce code omet complètement la partie de la chaîne que vous séparez des résultats, bien qu'il puisse être facilement modifié pour les inclure.

#include <string>
#include <vector>

void split(std::string str, std::string splitBy, std::vector<std::string>& tokens)
{
    /* Store the original string in the array, so we can loop the rest
     * of the algorithm. */
    tokens.Push_back(str);

    // Store the split index in a 'size_t' (unsigned integer) type.
    size_t splitAt;
    // Store the size of what we're splicing out.
    size_t splitLen = splitBy.size();
    // Create a string for temporarily storing the fragment we're processing.
    std::string frag;
    // Loop infinitely - break is internal.
    while(true)
    {
        /* Store the last string in the vector, which is the only logical
         * candidate for processing. */
        frag = tokens.back();
        /* The index where the split is. */
        splitAt = frag.find(splitBy);
        // If we didn't find a new split point...
        if(splitAt == string::npos)
        {
            // Break the loop and (implicitly) return.
            break;
        }
        /* Put everything from the left side of the split where the string
         * being processed used to be. */
        tokens.back() = frag.substr(0, splitAt);
        /* Push everything from the right side of the split to the next empty
         * index in the vector. */
        tokens.Push_back(frag.substr(splitAt+splitLen, frag.size()-(splitAt+splitLen)));
    }
}

Pour l'utiliser, il suffit d'appeler comme ça ...

std::string foo = "This is some string I want to split by spaces.";
std::vector<std::string> results;
split(foo, " ", results);

Vous pouvez désormais accéder à volonté à tous les résultats du vecteur. Aussi simple que cela - pas de stringstream, pas de bibliothèques tierces, pas de retour en C!

2
CodeMouse92

Qu'en est-il de la fonction erase()? Si vous connaissez la position exakt dans la chaîne où diviser, vous pouvez "extraire" les champs de la chaîne avec erase().

std::string date("01/02/2019");
std::string day(date);
std::string month(date);
std::string year(date);

day.erase(2, string::npos); // "01"
month.erase(0, 3).erase(2); // "02"
year.erase(0,6); // "2019"
0
Mubin Icyer

Y a-t-il une raison pour laquelle vous ne voulez pas convertir un string en un tableau de caractères (char*)? Il est assez facile d'appeler .c_str(). Vous pouvez également utiliser une boucle et la fonction .find().

classe de chaîne
chaîne .find ()
chaîne .c_str ()

0
collinjsimpson