web-dev-qa-db-fra.com

Différence entre effacer et supprimer

Je suis un peu confus quant à la différence entre l'utilisation de l'algorithme std :: remove. Plus précisément, je ne suis pas en mesure de comprendre ce qui est supprimé lorsque j'utilise cet algorithme. J'ai écrit un petit code de test comme ceci:

std::vector<int> a;
a.Push_back(1);
a.Push_back(2);

std::remove(a.begin(), a.end(), 1);


int s = a.size();

std::vector<int>::iterator iter = a.begin();
std::vector<int>::iterator endIter = a.end();

std::cout<<"Using iter...\n";
for(; iter != endIter; ++iter)
{
    std::cout<<*iter<<"\n";
}

std::cout<<"Using size...\n";
for(int i = 0; i < a.size(); ++i)
{
    std::cout<<a[i]<<"\n";
}

Le résultat était de 2,2 dans les deux cas.

Cependant, si j'utilise effacer avec supprimer quelque chose comme ceci:

a.erase(std::remove(a.begin(), a.end(), 1), a.end());

Je reçois la sortie en 2.

Donc mes questions sont:

(1). Est-ce que std :: remove est utilisé autrement que par la fonction erase?.

(2). Même après avoir effectué std :: remove, pourquoi a.size () renvoie 2 et non 1?

J'ai lu l'article du livre Effective STL de Scott Meyer sur l'idiome effacer-supprimer. Mais j'ai toujours cette confusion.

47
Naveen

remove() ne supprime pas réellement les éléments du conteneur - il ne fait que transmettre les éléments non supprimés au-dessus des éléments supprimés. La clé est de réaliser que remove() est conçu pour fonctionner non seulement sur un conteneur, mais sur toute paire d'itérateurs de transfert arbitraires: cela signifie que ne peut pas supprimer les éléments, car une paire d'itérateurs arbitraires ne t nécessairement avoir la possibilité de supprimer des éléments.

Par exemple, les pointeurs au début et à la fin d'un tableau C normal sont des itérateurs en aval et peuvent donc être utilisés avec remove():

int foo[100];

...

remove(foo, foo + 100, 42);    // Remove all elements equal to 42

Ici, il est évident que remove() ne peut pas redimensionner le tableau!

52
j_random_hacker

std::remove ne supprime pas les objets réels, mais les pousse à la fin du conteneur. La suppression et la désallocation de la mémoire se font par effacement. Alors:

(1). Est-ce que std :: remove est utilisé autrement que par la fonction erase?.

Oui, il est utile de placer une paire d’itérateurs dans une nouvelle séquence sans s’inquiéter de la bonne allocation, etc.

(2). Même après avoir effectué std :: remove, pourquoi a.size () renvoie 2 et non 1?

Le conteneur contient toujours ces objets, vous ne pouvez utiliser qu'un nouvel ensemble d'itérateurs. Par conséquent, la taille est toujours ce qu'elle était.

17
dirkgently

Qu'est-ce que std :: remove fait?

Voici le pseudo-code de std::remove. Prenez quelques secondes pour voir ce qui se passe, puis lisez l'explication.

Iter remove(Iter start, Iter end, T val) {
    Iter destination = start;

    //loop through entire list
    while(start != end) { 
        //skip element(s) to be removed
        if (*start == val) { 
            start++; 
         }
         else //retain rest of the elements
             *destination++ = *start++;
     }

     //return the new end of the list
     return destination;
}

Notez que supprimer a simplement déplacé les éléments de la séquence vers le haut, écrasant ainsi les valeurs que vous vouliez supprimer. Donc, les valeurs que vous vouliez supprimer ont bel et bien disparu, mais alors quel est le problème? Supposons que vous ayez un vecteur avec les valeurs {1, 2, 3, 4, 5}. Après avoir appelé remove pour val = 3, le vecteur a maintenant {1, 2, 4, 5, 5}. C'est-à-dire que 4 et 5 ont été déplacés vers le haut, de sorte que 3 est parti du vecteur mais la taille du vecteur n'a pas changé. En outre, la fin du vecteur contient maintenant une copie restante de 5.

Qu'est-ce que vector :: erase fait?

std::erase prend le début et la fin de la plage que vous souhaitez supprimer. Il ne prend pas la valeur que vous souhaitez supprimer, mais uniquement le début et la fin de la plage. Voici un pseudo-code pour son fonctionnement:

erase(Iter first, Iter last)
{
    //copy remaining elements from last
    while (last != end())
        *first++ = *last++;

   //truncate vector
   resize(first - begin());
}

Ainsi, l'opération d'effacement change réellement la taille du conteneur et libère ainsi de la mémoire. 

L'idiome supprimer-effacer

La combinaison de std::remove et std::erase vous permet de supprimer des éléments correspondants du conteneur afin que ce dernier soit tronqué si les éléments étaient supprimés. Voici comment le faire:

//first do the remove
auto removed = std::remove(vec.begin(), vec.end(), val);

//now truncate the vector
vec.erase(removed, vec.end());

Ceci est connu comme idiome remove-erase. Pourquoi est-ce conçu comme ça? L’idée est que l’opération de recherche d’éléments est plus générique et indépendante du conteneur sous-jacent (dépend uniquement des itérateurs). Cependant, l'opération d'effacement dépend de la manière dont le conteneur stocke la mémoire (par exemple, vous pourriez avoir une liste chaînée au lieu d'un tableau dynamique). STL s'attend donc à ce que les conteneurs fassent leur propre effacement tout en fournissant une opération "remove" générique afin que tous les conteneurs n'aient pas à implémenter ce code. À mon avis, le nom est très trompeur et std::remove aurait dû s'appeler std::find_move.

Remarque: Le code ci-dessus est strictement pseudocode. L'implémentation réelle de STL est plus intelligente, par exemple, en utilisant std::move au lieu de copier.

12
Shital Shah

Le plus simple que je puisse venir avec:

erase() est quelque chose que vous pouvez faire pour un élément dans un conteneur. Étant donné un itérateur/index dans un conteneur, erase( it ) supprime le conteneur auquel se réfère l'itérateur.

remove() est quelque chose que vous pouvez faire pour une plage, il réorganise cette plage mais ne supprime rien.

6
anon

j'ai fait face au même problème, essayant de comprendre la différence ... Les explications qui ont été données jusqu'ici sont exactes, mais je ne les ai comprises qu'après avoir vu un exemple.

#include <algorithm>
#include <string>
#include <iostream>
#include <cctype>

int main()
{
    std::string str1 = "Text with some   spaces";
    std::string::iterator it = remove(str1.begin(), str1.end(), 't');
    std::cout << str1 << std::endl;// prints "Tex wih some   spaceses"
    for (str1.begin();it != str1.end(); ++it) 
    {
         std::cout << *it; //prints "es"
    }

}

comme vous pouvez le constater, la suppression ne déplace que le minuscule 't' à la fin de la chaîne, tout en renvoyant un nouvel itérateur à la fin de la nouvelle chaîne (nouvelle chaîne est l'ancienne chaîne jusqu'à l'endroit où l'élément supprimé est inséré ) C’est pourquoi, lorsque vous imprimez l’itérateur que vous avez obtenu de "remove"

   "Text with some   spaces"
       ^   ^removes both 't', then shift all elements forward -1 //what we want to remove
   "Text with some   spaces"
                          ^ end of string                    -2 //original state of string
   "Tex with some   spacess"
                          ^end of string                     -3 //first 't' removed
   "Tex wih some   spaceses"
                          ^end of string                     -4 //second 't' removed
   "Tex wih some   spaceses"
                        ^new iterator that remove() returned -5 // the state of string after "remove" and without "erase"

si vous passez l'itérateur que vous avez obtenu à l'étape 5 à "erase ()", il saura effacer de là à la fin de la chaîne en redimensionnant la chaîne en cours de traitement 

6
Molegrammer

supprimer ne "supprime" pas vraiment n'importe quoi, parce que ça ne peut pas.

Afin de "réellement" supprimer les éléments du conteneur, vous devez accéder aux API de conteneur. Where as remove fonctionne uniquement avec des itérateurs, quels que soient les conteneurs indiqués par ces itérateurs. Par conséquent, même si remove veut une "suppression réelle", il ne le peut pas. 

Supprimez les éléments "supprimés" par les éléments suivants qui n'ont pas été supprimés, puis l'appelant décidera d'utiliser la nouvelle valeur logique end renvoyée à la place de la valeur end d'origine.

Dans votre cas, supprimez logiquement le 1 de vectora supprimé, mais la taille reste à 2 elle-même. Effacer réellement supprimé les éléments du vecteur. [du vecteur new end à old end]

L'idée principale de remove est qu'elle ne peut pas changer le nombre d'éléments et qu'elle supprime simplement les éléments d'une plage selon des critères. 

3
aJ.

Pour supprimer un élément avec une condition (égale à une valeur ou à une condition inférieure à) dans le conteneur tel que vector, il combine toujours la fonction membre function erase et std::remove ou std::remove_if.

En vectoriel, la fonction erase peut simplement supprimer élément par position, comme:

effacement de l'itérateur (position de l'itérateur);

iterator erase (itérateur en premier, itérateur en dernier);

Mais si vous voulez effacer des éléments avec certaines conditions, vous pouvez les combiner avec std::remove ou std::remove_if.

Par exemple, vous voulez effacer tous les éléments 6 dans le vecteur ci-dessous:

std::vector<int> vec{6, 8, 10, 3, 4, 5, 6, 6, 6, 7, 8};
// std::remove move elements and return iterator for vector erase funtion
auto last = std::remove(vec.begin(), vec.end(), 6);
for(int a:vec)
    cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 6 6 7 8 

vec.erase(last, vec.end());
for(int a:vec)
    cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 

std::remove fonctionne comme ci-dessous, il n'efface aucun élément, il ne fait que déplacer des éléments et renvoie l'itérateur.

 enter image description here

Mise en oeuvre possible:

template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
    first = std::find(first, last, value);
    if (first != last)
        for(ForwardIt i = first; ++i != last; )
            if (!(*i == value))
                *first++ = std::move(*i);
    return first;
}

Conclusion:

Si vous souhaitez supprimer des éléments avec certaines conditions, vous utilisez essentiellement vector::iterator erase (iterator first, iterator last);.

Première lecture du début de la plage:

auto last = std :: remove (vec.begin (), vec.end (), equal_condition_value);

effacer par plage (toujours avec end ())

vec.erase (last, vec.end ());

cité:

https://en.cppreference.com/w/cpp/algorithm/remove

0
Jayhello