[ERASE - REMOVE IDIOM] string_trim

Le problème exposé dans ce sujet a été résolu.

Salut les agrumes. C’est mon premier post sur le forum. Je suis donc un vrai bleu. Je suis entrain de faire un exercice du cours c++ moderne. C’est l’exercice string_strim:

Le but est de reproduire une fonction qui existe dans de nombreux autres langages, comme C# ou Python, et qui permet de supprimer les espaces, au début et à la fin d’une chaîne de caractères. Cela signifie non seulement les espaces ' ' mais aussi les tabulations '\t' , les retours à la ligne '\n' , etc.

Je l’avais déjà fait mais cette fois j’ai un peu changé d’approche. Même si je ne me rappel plus exactement comment j’ai fait la dernière fois. Je sais que la méthode ne m’avait pas trop plus. Un peu verbeuse a mon gout. J’ai donc essayer d’écrire quelque chose d’un plus logique cette fois ci. Le problème c’est que ça a l’air de marcher (c’est pas moins verbeux au final) mais la réponse est un peu accidentelle et je n’arrive pas à la comprendre.

Voici mon code source:

#include <iostream>
#include <algorithm>

using namespace std::literals;

int main(int argc, char const *argv[])
{
    std::string chaine { R"&( 
        
                                Un tas de chose ... voila 
    .. Evidemment ca ne s'arrete pas la!!   
     FIN Fin   fin. ... !!$                  )&" };
    
    auto beginChaine { std::begin( chaine ) };
    auto endChaine { std::end( chaine ) };

    auto it { std::find_if_not( beginChaine, endChaine, isspace ) };
    std::cout << *beginChaine << "...." << *it << "..." << *( endChaine - 1 ) << std::endl;
    chaine.erase( std::remove_if( beginChaine, it, isspace ), it );
    std::cout << *beginChaine << "...." << *it << "..." << *( endChaine - 1 ) << std::endl;

    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;
    
    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );

    std::reverse( beginChaine, endChaine );

    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;

    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );

    it = std::find_if_not( beginChaine, endChaine, isspace );
    std::cout << *beginChaine << "...." << *it << "..." << *( endChaine - 1 ) << std::endl;
    chaine.erase( std::remove_if( beginChaine, it, isspace ), it );
    std::cout << *beginChaine << "...." << *it << "..." << *( endChaine - 1 ) << std::endl;


    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );
    std::reverse( beginChaine, endChaine );

  
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;
    return 0;
}

Et la version sans les superflus:

#include <iostream>
#include <algorithm>

using namespace std::literals;

int main(int argc, char const *argv[])
{
    std::string chaine { R"&( 
        
                                Un tas de chose ... voila 
    .. Evidemment ca ne s'arrete pas la!!   
     FIN Fin   fin. ... !!$                  )&" };
    
    auto beginChaine { std::begin( chaine ) };
    auto endChaine { std::end( chaine ) };
    auto it { std::find_if_not( beginChaine, endChaine, isspace ) };

    chaine.erase( std::remove_if( beginChaine, it, isspace ), it );

    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );

    std::reverse( beginChaine, endChaine );

    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );
    it = std::find_if_not( beginChaine, endChaine, isspace );

    chaine.erase( std::remove_if( beginChaine, it, isspace ), it );

    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );
    std::reverse( beginChaine, endChaine );
  
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;
    return 0;
}

ce que je n’arrive précisément pas à comprendre, c’est que erase(itR, it) ca a l’air d’effacer à contre sens. C’est bizarre. J’ai l’impression de passer à côté de quelque chose de bête mais je vois pas.

S’il vous plait. Merci :) !

+0 -0

Bonjour,

Je ne suis pas sur de comprendre ce que tu entends par effacer à contre sens…

Mais ce que je peux te dire, c’est que remove/remove_if déplacent tous tes espaces pour les mettre avant ton second paramètre (il les met avant 'it' dans ton cas ) et te retourne un forward iterator que tu peux voir comme une séquence d’éléments, ce qui correspond à ce que tu "veux" supprimer. Mais ça ne fait pas la suppression.

C’est la qu’entre en jeu erase, son but et d’effacer l’élément à la position passée en paramètre et "recoller" le reste à la suite. Dans le cas d’un forward iterator, tu peux voir ça comme un range, il va juste parcourir ton itérateur et supprimer tous les éléments que tu ne voulais plus.

Après, il n’est pas précisé dans quel sens tes éléments seront supprimés. Mais le standard dit qu’il y aura la suppression de tes éléments et qu’il y aura un nombre de move équivalent au nombre d’éléments a garder qui se situent après les éléments supprimés.

Salut,

2 petites remarques :

  • Tu n’as pas besoin du erase-remove idiom puisque tu fais un find_if pour trouver le premier caractère qui n’est pas un espace, donc forcément de begin à it ce sont des espaces, tu peux les supprimer directement.
  • au lieu de renverser ta chaine entièrement pour réappliquer le même algorithme (et la re-inverser pour avoir le résultat correct), tu peux renverser seulement ta manière de la parcourir, en utilisant les reverse_iterator (en l’occurence il te suffit d’initialiser beginChaine et endChaine avec std::rbegin et std::rend)
+1 -0

Merci romantik

Concernant la première remarque:

Tu n’as pas besoin du erase-remove idiom puisque tu fais un find_if pour trouver le premier caractère qui n’est pas un espace, donc forcément de begin à it ce sont des espaces, tu peux les supprimer directement.

Les supprimer directement? Je vais voir ce que ca donne je ne sais plus. Je crois que t’as raison. Pour autant tu peux me dire ce qui se passe réellement? je viens d’essayer avec un petit schéma (illustration en fin de post) pas à pas. J’ai l’impression que le remove ne fait rien. Je me trompe peut être.

Pour la seconde:

au lieu de renverser ta chaine entièrement pour réappliquer le même algorithme (et la re-inverser pour avoir le résultat correct), tu peux renverser seulement ta manière de la parcourir, en utilisant les reverse_iterator (en l’occurence il te suffit d’initialiser beginChaine et endChaine avec std::rbegin et std::rend)

c’est ce que je vais faire en faite. C’est a peu pres comme ca que je l’avais fait la premiere fois je crois. Seulement j’aimerais comprendre ce que j’ai fait cette fois si.

JusDePom. Merci. t’es allé chercher dans le détail pour ton explication je t’en remercie.

Déjà pour m’expliquer

Je ne suis pas sur de comprendre ce que tu entends par effacer à contre sens…

je donne à erase deux paramètres de type forward iterator donc je suppose qu’il est censé supprimer dans le sens "croissant" des itérateurs. À l’analyse, l’itérateur first que je lui donne est plus "grand" que l’itérateur "last". J’espère m’être mieux exprimé.

Mais ce que je peux te dire, c’est que remove/remove_if déplacent tous tes espaces pour les mettre avant ton second paramètre (il les met avant 'it' dans ton cas ) et te retourne un forward iterator que tu peux voir comme une séquence d’éléments, ce qui correspond à ce que tu "veux" supprimer. Mais ça ne fait pas la suppression.

Oui ca ne fait pas la suppression. Je viens en effet de relire la doc. Et s’aggissant de erase, j’ai plus l’impression qu’il ne deplace pas mes espaces, mais qu' il les remplace par ce qu’il y avant eux (c’est dans le schema que je me suis fait pour mieux comprendre). Ceci dit je crois que je comprends un peu mieux quoi faire maitenant.

C’est en vous lisant, romantik et JusDePom, et en relisant la doc que j’ai fais ceci.

schema de comprehension de remove/remove_if

erase prend first et it en parametre et retourne first. Dans mon cas ca ne change rien. En effet i (l’increment) vaudra it (le last) quand la valeur de *i verifiera !isspace. Donc tu as problablement raison romantik.

NB:Les underscore il faut les voir comme des espaces. Je reviens vers vous apres avoir reecrit mon programme. Merci encore :) .

+0 -0

Voila ca a completement marche. Pour ce qui est d’inverser la chaine au lieu de la facon de la parcourir c’etait pas vraiment le sujet mais comme le conseil etait tres judicieux. Je vais le faire tout de suite. Encore merci a vous.

#include <iostream>
#include <algorithm>

using namespace std::literals;

int main(int argc, char const *argv[])
{
    std::string chaine { R"&( 
        
                                Un tas de chose ... voila 
    .. Evidemment ca ne s'arrete pas la!!   
     FIN Fin   fin. ... !!$                  )&" };
    
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl << std::endl << std::endl;

    auto beginChaine { std::begin( chaine ) };
    auto endChaine { std::end( chaine ) };
    auto it { std::find_if_not( beginChaine, endChaine, isspace ) };

    chaine.erase( beginChaine, it );

    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl << std::endl << std::endl;
    
    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );

    std::reverse( beginChaine, endChaine );


    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );
    it = std::find_if_not( beginChaine, endChaine, isspace );

    chaine.erase( beginChaine, it );


    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );

    std::reverse( beginChaine, endChaine );
  
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;
    return 0;
}
+0 -0

Juste pour ajouter une dernière chose. erase n’a apparemment pas de surcharge pour les reverse iterator. A voir si ca ne sera pas intéressant d’en faire un exercice quand j’aurais un plus avancée sur le tutoriel.

#include <iostream>
#include <algorithm>

using namespace std::literals;

int main(int argc, char const *argv[])
{
    std::string chaine { R"&( 
        
                                Un tas de chose ... voila 
    .. Evidemment ca ne s'arrete pas la!!   
     FIN Fin   fin. ... !!$                  )&" };
    
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl << std::endl << std::endl;

    auto beginChaine { std::begin( chaine ) };
    auto endChaine { std::end( chaine ) };
    auto it { std::find_if_not( beginChaine, endChaine, isspace ) };

    chaine.erase( beginChaine, it );

    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl << std::endl << std::endl;
    
    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );


    auto rbeginChaine = std::rbegin( chaine );
    auto rendChaine = std::rend( chaine );
    auto rit = std::find_if_not( rbeginChaine, rendChaine, isspace );
    
    //il y a problème là
    chaine.erase( rbeginChaine, rit );


    beginChaine = std::begin( chaine );
    endChaine = std::end( chaine );
  
    std::cout << chaine << R"&(----|C'est la fin de la chaine|----)&" << std::endl;
    return 0;
}
+0 -0

au temps pour moi, effectivement apparamment tu ne peux pas appeler erase sur des reverse_iterator, ça doit être trop galère à gérer pour raligner en mémoire je suppose

Tu n’as pas de move qui s’effectue là car ton prédicat est vérifié sur tout les éléments de la séquence que tu lui demandes (ce sont tous des whitespaces) et donc ton first est toujours au début de la chaine, donc tu demandes bien à erase d’effacer de begin à it

+1 -0

Juste pour ajouter une dernière chose. erase n’a apparemment pas de surcharge pour les reverse iterator. A

En fait tu n’en as pas vraiment besoin. Depuis un reverse iterator, tu peux toujours utiliser la méthode base() pour revenir à l’itérateur d’origine.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte