Analyser (fractionner) une chaîne en C ++ à l'aide d'un délimiteur de chaîne (C ++ standard)


364

J'analyse une chaîne en C ++ en utilisant ce qui suit:

using namespace std;

string parsed,input="text to be parsed";
stringstream input_stringstream(input);

if (getline(input_stringstream,parsed,' '))
{
     // do some processing.
}

L'analyse avec un seul délimiteur de caractères est correcte. Mais que faire si je veux utiliser une chaîne comme délimiteur.

Exemple: je veux partager:

scott>=tiger

avec >=comme délimiteur afin que je puisse obtenir scott et tigre.

Réponses:


577

Vous pouvez utiliser la std::string::find()fonction pour trouver la position de votre délimiteur de chaîne, puis utiliser std::string::substr()pour obtenir un jeton.

Exemple:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • La find(const string& str, size_t pos = 0)fonction renvoie la position de la première occurrence de strdans la chaîne, ou npossi la chaîne n'est pas trouvée.

  • La substr(size_t pos = 0, size_t n = npos)fonction renvoie une sous-chaîne de l'objet, commençant à la position poset à la longueur npos.


Si vous avez plusieurs délimiteurs, après avoir extrait un jeton, vous pouvez le supprimer (délimiteur inclus) pour poursuivre les extractions suivantes (si vous souhaitez conserver la chaîne d'origine, utilisez simplement s = s.substr(pos + delimiter.length());):

s.erase(0, s.find(delimiter) + delimiter.length());

De cette façon, vous pouvez facilement boucler pour obtenir chaque jeton.

Exemple complet

std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";

size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
    token = s.substr(0, pos);
    std::cout << token << std::endl;
    s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;

Production:

scott
tiger
mushroom

66
Pour ceux qui ne veulent pas modifier la chaîne d'entrée, faitessize_t last = 0; size_t next = 0; while ((next = s.find(delimiter, last)) != string::npos) { cout << s.substr(last, next-last) << endl; last = next + 1; } cout << s.substr(last) << endl;
hayk.mart

30
REMARQUE: mushroomsorties en dehors de la boucle, c'ests = mushroom
Don Larynx

1
Ces exemples n'extraient pas le dernier jeton de la chaîne. Un échantillon de la mienne extraire un IpV4 d'une chaîne: <code> size_t last = 0; size_t next = 0; int index = 0; while (index <4) {next = str.find (délimiteur, dernier); numéro automatique = str.substr (dernier, suivant - dernier); IPv4 [index ++] = atoi (number.c_str ()); dernier = suivant + 1; } </code>
rfog

2
@ hayk.mart Juste une note, ce serait la suivante, vous devez ajouter 2 pas 1 en raison de la taille du délimiteur qui est de 2 caractères :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; size_t last = 0; size_t next = 0; while ((next = s.find (délimiteur, last))! = std :: string :: npos) {std :: cout << s.substr (last, next-last) << std :: endl; dernier = suivant + 2; } std :: cout << s.substr (last) << std :: endl;
ervinbosenbacher

Afin d'obtenir "tigre", utilisez std::string token = s.substr(s.find(delimiter) + 1);, si vous êtes sûr qu'il existe (j'utilise +1 dans la longueur) ...
gsamaras

64

Cette méthode utilise std::string::findsans muter la chaîne d'origine en se souvenant du début et de la fin du jeton de sous-chaîne précédent.

#include <iostream>
#include <string>

int main()
{
    std::string s = "scott>=tiger";
    std::string delim = ">=";

    auto start = 0U;
    auto end = s.find(delim);
    while (end != std::string::npos)
    {
        std::cout << s.substr(start, end - start) << std::endl;
        start = end + delim.length();
        end = s.find(delim, start);
    }

    std::cout << s.substr(start, end);
}

34

Vous pouvez utiliser la fonction suivante pour fractionner la chaîne:

vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        if (!token.empty()) tokens.push_back(token);
        prev = pos + delim.length();
    }
    while (pos < str.length() && prev < str.length());
    return tokens;
}

5
IMO cela ne fonctionne pas comme prévu: split("abc","a")retournera un vecteur ou une seule chaîne "bc", où je pense que cela aurait plus de sens s'il avait renvoyé un vecteur d'éléments ["", "bc"]. En utilisant str.split()Python, il était intuitif pour moi de renvoyer une chaîne vide au cas où elle delimserait trouvée au début ou à la fin, mais c'est juste mon opinion. Quoi qu'il en soit, je pense simplement que cela devrait être mentionné
kyriakosSt

1
Je recommande fortement de supprimer la if (!token.empty()) prévention du problème mentionné par @kyriakosSt ainsi que d'autres problèmes liés aux délimiteurs consécutifs.
Steve

1
Je supprimerais mon vote positif si je le pouvais, mais SO ne me le permettra pas. Le problème soulevé par @kyriakosSt est un problème, et la suppression if (!token.empty())ne semble pas suffire à le résoudre.
bhaller

1
@bhaller ce sniplet a été conçu exactement pour ignorer les fragments vides. Si vous devez en conserver des vides, je crains que vous n'ayez besoin d'écrire une autre implémentation fractionnée. Veuillez vous suggérer de le poster ici pour le bien de la communauté.
Sviatoslav

32

Pour le délimiteur de chaîne

Chaîne fractionnée basée sur un délimiteur de chaîne . Tels que le fractionnement de chaîne en "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"fonction du délimiteur de chaîne "-+", la sortie sera{"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

// for string delimiter
vector<string> split (string s, string delimiter) {
    size_t pos_start = 0, pos_end, delim_len = delimiter.length();
    string token;
    vector<string> res;

    while ((pos_end = s.find (delimiter, pos_start)) != string::npos) {
        token = s.substr (pos_start, pos_end - pos_start);
        pos_start = pos_end + delim_len;
        res.push_back (token);
    }

    res.push_back (s.substr (pos_start));
    return res;
}

int main() {
    string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
    string delimiter = "-+";
    vector<string> v = split (str, delimiter);

    for (auto i : v) cout << i << endl;

    return 0;
}


Production

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Pour le délimiteur à un seul caractère

Chaîne fractionnée basée sur un délimiteur de caractères. Tels que le fractionnement de chaîne "adsf+qwer+poui+fdgh"avec délimiteur "+"affichera{"adsf", "qwer", "poui", "fdg"h}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

vector<string> split (const string &s, char delim) {
    vector<string> result;
    stringstream ss (s);
    string item;

    while (getline (ss, item, delim)) {
        result.push_back (item);
    }

    return result;
}

int main() {
    string str = "adsf+qwer+poui+fdgh";
    vector<string> v = split (str, '+');

    for (auto i : v) cout << i << endl;

    return 0;
}


Production

adsf
qwer
poui
fdgh

Vous revenez, vector<string>je pense que cela appellera le constructeur de copie.
Mayur

2
Chaque référence que j'ai vue montre que l'appel au constructeur de copie est éliminé dans ce contexte.
David Given

Avec les compilateurs "modernes" (C ++ 03?) Je crois que c'est correct, RVO et / ou déplacer la sémantique éliminera le constructeur de copie.
Kevin

J'ai essayé celui pour le délimiteur à caractère unique, et si la chaîne se termine par un délimiteur (c'est-à-dire une colonne csv vide à la fin de la ligne), elle ne renvoie pas la chaîne vide. Il renvoie simplement une chaîne de moins. Par exemple: 1,2,3,4 \ nA, B, C,
kounoupis

J'ai également essayé celui pour le délimiteur de chaîne, et si la chaîne se termine par un délimiteur, le dernier délimiteur fait partie de la dernière chaîne extraite.
kounoupis

20

Ce code sépare les lignes du texte et ajoute tout le monde dans un vecteur.

vector<string> split(char *phrase, string delimiter){
    vector<string> list;
    string s = string(phrase);
    size_t pos = 0;
    string token;
    while ((pos = s.find(delimiter)) != string::npos) {
        token = s.substr(0, pos);
        list.push_back(token);
        s.erase(0, pos + delimiter.length());
    }
    list.push_back(s);
    return list;
}

Appelé par:

vector<string> listFilesMax = split(buffer, "\n");

ça marche très bien! J'ai ajouté list.push_back (s); car il manquait.
Stoica Mircea

1
il manque la dernière partie de la chaîne. Une fois la boucle while terminée, nous devons ajouter le reste de s en tant que nouveau jeton.
whihathac

J'ai apporté une modification à l'exemple de code pour corriger le push_back manquant.
frette

1
Ce sera plus agréablevector<string> split(char *phrase, const string delimiter="\n")
Mayur

15

strtok vous permet de passer plusieurs caractères comme délimiteurs. Je parie que si vous passez "> =", votre exemple de chaîne sera correctement divisé (même si les> et = sont comptés comme des délimiteurs individuels).

EDITER si vous ne voulez pas utiliser c_str()pour convertir de chaîne en char *, vous pouvez utiliser substr et find_first_of pour tokenize.

string token, mystring("scott>=tiger");
while(token != mystring){
  token = mystring.substr(0,mystring.find_first_of(">="));
  mystring = mystring.substr(mystring.find_first_of(">=") + 1);
  printf("%s ",token.c_str());
}

3
Merci. Mais je veux utiliser uniquement C ++ et pas toutes les fonctions C comme strtok()cela m'obligerait à utiliser un tableau de caractères au lieu d'une chaîne.
TheCrazyProgrammer

2
@TheCrazyProgrammer Alors? Si une fonction C fait ce dont vous avez besoin, utilisez-la. Ce n'est pas un monde où les fonctions C ne sont pas disponibles en C ++ (en fait, elles doivent l'être). .c_str()est bon marché et facile aussi.
Qix - MONICA A ÉTÉ BRUÉE

1
La vérification de si (token! = Mystring) donne des résultats erronés si vous avez des éléments répétitifs dans votre chaîne. J'ai utilisé votre code pour créer une version qui n'en a pas. Il y a beaucoup de changements qui changent fondamentalement la réponse, j'ai donc écrit ma propre réponse au lieu de la modifier. Vérifiez-le ci-dessous.
Amber Elferink

5

Voici mon point de vue à ce sujet. Il gère les cas marginaux et prend un paramètre facultatif pour supprimer les entrées vides des résultats.

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.size() >= suffix.size() &&
           s.substr(s.size() - suffix.size()) == suffix;
}

std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool& removeEmptyEntries = false)
{
    std::vector<std::string> tokens;

    for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
    {
         size_t position = s.find(delimiter, start);
         end = position != string::npos ? position : s.length();

         std::string token = s.substr(start, end - start);
         if (!removeEmptyEntries || !token.empty())
         {
             tokens.push_back(token);
         }
    }

    if (!removeEmptyEntries &&
        (s.empty() || endsWith(s, delimiter)))
    {
        tokens.push_back("");
    }

    return tokens;
}

Exemples

split("a-b-c", "-"); // [3]("a","b","c")

split("a--c", "-"); // [3]("a","","c")

split("-b-", "-"); // [3]("","b","")

split("--c--", "-"); // [5]("","","c","","")

split("--c--", "-", true); // [1]("c")

split("a", "-"); // [1]("a")

split("", "-"); // [1]("")

split("", "-", true); // [0]()

4

Cela devrait fonctionner parfaitement pour les délimiteurs de chaîne (ou de caractère unique). N'oubliez pas d'inclure #include <sstream>.

std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+"; 
std::istringstream ss(input);
std::string token;
std::string::iterator it;

while(std::getline(ss, token, *(it = delimiter.begin()))) {
    while(*(++it)) ss.get();
    std::cout << token << " " << '\n';
}

La première boucle while extrait un jeton en utilisant le premier caractère du délimiteur de chaîne. La seconde boucle while ignore le reste du délimiteur et s'arrête au début du jeton suivant.


3

J'utiliserais boost::tokenizer. Voici la documentation expliquant comment créer une fonction de tokenizer appropriée: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Voici celui qui fonctionne pour votre cas.

struct my_tokenizer_func
{
    template<typename It>
    bool operator()(It& next, It end, std::string & tok)
    {
        if (next == end)
            return false;
        char const * del = ">=";
        auto pos = std::search(next, end, del, del + 2);
        tok.assign(next, pos);
        next = pos;
        if (next != end)
            std::advance(next, 2);
        return true;
    }

    void reset() {}
};

int main()
{
    std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
    for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
        std::cout << i << '\n';
}

3
Merci. Mais je ne souhaite que du C ++ standard et non une bibliothèque tierce.
TheCrazyProgrammer

@TheCrazyProgrammer: D'accord, quand j'ai lu "Standard C ++", je pensais que cela ne signifiait pas d'extensions non standard, pas que vous ne pouviez pas utiliser des bibliothèques tierces conformes aux normes.
Benjamin Lindley

3

La réponse est déjà là, mais la réponse sélectionnée utilise la fonction d'effacement qui est très coûteuse, pensez à une très grosse chaîne (en Mo). Par conséquent, j'utilise la fonction ci-dessous.

vector<string> split(const string& i_str, const string& i_delim)
{
    vector<string> result;

    size_t found = i_str.find(i_delim);
    size_t startIndex = 0;

    while(found != string::npos)
    {
        string temp(i_str.begin()+startIndex, i_str.begin()+found);
        result.push_back(temp);
        startIndex = found + i_delim.size();
        found = i_str.find(i_delim, startIndex);
    }
    if(startIndex != i_str.size())
        result.push_back(string(i_str.begin()+startIndex, i_str.end()));
    return result;      
}

J'ai testé cela, et cela fonctionne. Merci! À mon avis, c'est la meilleure réponse car, comme l'indique le répondeur d'origine, cette solution réduit la surcharge de mémoire et le résultat est commodément stocké dans un vecteur. (reproduit la string.split()méthode Python .)
Robbie Capps

2

Il s'agit d'une méthode complète qui fractionne la chaîne sur n'importe quel délimiteur et renvoie un vecteur des chaînes hachées.

C'est une adaptation de la réponse de ryanbwork. Cependant, sa vérification pour: if(token != mystring)donne de mauvais résultats si vous avez des éléments répétitifs dans votre chaîne. Ceci est ma solution à ce problème.

vector<string> Split(string mystring, string delimiter)
{
    vector<string> subStringList;
    string token;
    while (true)
    {
        size_t findfirst = mystring.find_first_of(delimiter);
        if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
        {
            subStringList.push_back(mystring); //push back the final piece of mystring
            return subStringList;
        }
        token = mystring.substr(0, mystring.find_first_of(delimiter));
        mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
        subStringList.push_back(token);
    }
    return subStringList;
}

1
Quelque chose comme ça while (true)fait généralement peur à voir dans un morceau de code comme celui-ci. Personnellement, je recommanderais de réécrire ceci afin que la comparaison avec std::string::npos(ou respectivement une vérification par rapport mystring.size()) rende while (true)obsolète.
Joel Bodenmann

1

Si vous ne souhaitez pas modifier la chaîne (comme dans la réponse de Vincenzo Pii) et que vous souhaitez également afficher le dernier jeton, vous pouvez utiliser cette approche:

inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
    std::vector<std::string> ret;
    size_t start = 0;
    size_t end = 0;
    size_t len = 0;
    std::string token;
    do{ end = s.find(delimiter,start); 
        len = end - start;
        token = s.substr(start, len);
        ret.emplace_back( token );
        start += len + delimiter.length();
        std::cout << token << std::endl;
    }while ( end != std::string::npos );
    return ret;
}

0
#include<iostream>
#include<algorithm>
using namespace std;

int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}

void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}

int main(){

string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);

for(int i=0;i<x;i++)
cout<<res[i]<<endl;
  return 0;
}

PS: ne fonctionne que si les longueurs des chaînes après fractionnement sont égales


Cela utilise l'extension GCC - tableau de longueur variable.
user202729

0

Une fonction:

std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
    std::vector<std::string> vRet;
    int nPos = 0;
    int nLen = sWhat.length();
    int nDelimLen = sDelim.length();
    while (nPos < nLen) {
        std::size_t nFoundPos = sWhat.find(sDelim, nPos);
        if (nFoundPos != std::string::npos) {
            std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
            vRet.push_back(sToken);
            nPos = nFoundPos + nDelimLen;
            if (nFoundPos + nDelimLen == nLen) { // last delimiter
                vRet.push_back("");
            }
        } else {
            std::string sToken = sWhat.substr(nPos, nLen - nPos);
            vRet.push_back(sToken);
            break;
        }
    }
    return vRet;
}

Tests unitaires:

bool UnitTestSplit::run() {
bool bTestSuccess = true;

    struct LTest {
        LTest(
            const std::string &sStr,
            const std::string &sDelim,
            const std::vector<std::string> &vExpectedVector
        ) {
            this->sStr = sStr;
            this->sDelim = sDelim;
            this->vExpectedVector = vExpectedVector;
        };
        std::string sStr;
        std::string sDelim;
        std::vector<std::string> vExpectedVector;
    };
    std::vector<LTest> tests;
    tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
    tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
    tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));

    for (int i = 0; i < tests.size(); i++) {
        LTest test = tests[i];
        std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
        std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
        compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
        int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
        for (int n = 0; n < nMin; n++) {
            compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
        }
    }

    return bTestSuccess;
}

0
std::vector<std::string> parse(std::string str,std::string delim){
    std::vector<std::string> tokens;
    char *str_c = strdup(str.c_str()); 
    char* token = NULL;

    token = strtok(str_c, delim.c_str()); 
    while (token != NULL) { 
        tokens.push_back(std::string(token));  
        token = strtok(NULL, delim.c_str()); 
    }

    delete[] str_c;

    return tokens;
}

-4
std::vector<std::string> split(const std::string& s, char c) {
  std::vector<std::string> v;
  unsigned int ii = 0;
  unsigned int j = s.find(c);
  while (j < s.length()) {
    v.push_back(s.substr(i, j - i));
    i = ++j;
    j = s.find(c, j);
    if (j >= s.length()) {
      v.push_back(s.substr(i, s,length()));
      break;
    }
  }
  return v;
}

1
Veuillez être plus précis. Votre code ne se compilera pas. Voir la déclaration de "i" et la virgule au lieu d'un point.
jstuardo
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.