Comment supprimer un élément d'un vecteur stl avec une certaine valeur?

145

Je regardais la documentation de l'API pour le vecteur stl, et j'ai remarqué qu'il n'y avait aucune méthode sur la classe vector qui permettait de supprimer un élément avec une certaine valeur. Cela semble être une opération courante, et il semble étrange qu'il n'y ait pas de méthode intégrée pour le faire.

Bradtgmurray
la source
2
Je sais que j'ai mentionné cela plusieurs fois auparavant, mais le livre de Scott Meyer, Effective STL, couvre ces pièges de manière claire.
Rob Wells le
1
En relation: stackoverflow.com/questions/3385229/…
bobobobo
Cela pourrait être une lecture intéressante pour vous: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
sergiol

Réponses:

165

std::removen'efface pas réellement l'élément du conteneur, mais il renvoie le nouvel itérateur de fin qui peut être passé à container_type::erasepour effectuer la suppression REELLE des éléments supplémentaires qui se trouvent maintenant à la fin du conteneur:

std::vector<int> vec;
// .. put in some values ..
int int_to_remove = n;
vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());
Jim Buck
la source
3
Cette forme d'instruction tout-en-un dépend-elle de l'ordre dans lequel le compilateur évalue les arguments, ou est-elle vec.end()garantie d'être la même de chaque côté de l'appel à std::remove? Il me semble que la lecture d'autres parties du Web comme celle-ci est sûre, mais cela devrait être clairement indiqué.
dmckee --- ex-moderator chaton
1
C'est bon. Le résultat de vec.end()n'a pas besoin d'être le même; il doit juste être correct (ce qui est le cas).
Jim Buck
8
vec.end()doit être identique, mais ce std::removen'est pas grave car cela ne change rien. S'il le modifiait (et invalide l'ancienne valeur), alors il y aurait un problème: l'ordre d'évaluation des paramètres n'est pas spécifié et donc vous ne sauriez pas si la seconde vec.end()est toujours valide au moment où elle est utilisée. La raison pour laquelle c'est la même chose est simple, std::removene change pas la taille du conteneur, cela déplace simplement le contenu.
Steve Jessop le
2
J'ai trouvé cette question importante, car j'ai le même problème. Mais, mon studio visuel prend std::removeavec un seul argument; c'est ça const char *_Filename. Quelle méthode dois-je appeler?
Victor
15
C'est la version removequi supprime un fichier. Vous devez inclure <algorithm>pour accéder à la version de removequi traite des conteneurs.
Jim Buck
64

Si vous souhaitez supprimer un élément, ce qui suit sera un peu plus efficace.

std::vector<int> v;


auto it = std::find(v.begin(), v.end(), 5);
if(it != v.end())
    v.erase(it);

ou vous pouvez éviter les frais généraux liés au déplacement des articles si la commande ne compte pas pour vous:

std::vector<int> v;

auto it = std::find(v.begin(), v.end(), 5);

if (it != v.end()) {
  using std::swap;

  // swap the one to be removed with the last element
  // and remove the item at the end of the container
  // to prevent moving all items after '5' by one
  swap(*it, v.back());
  v.pop_back();
}
Éthérealone
la source
3
Notez que cela ne supprimera pas les doublons de l'élément s'ils existent, contrairement à l'approche std :: remove_if.
Den-Jason
15

Utilisez la méthode globale std :: remove avec l'itérateur de début et de fin, puis utilisez std :: vector.erase pour supprimer réellement les éléments.

Liens de documentation
std :: remove http://www.cppreference.com/cppalgorithm/remove.html
std :: vector.erase http://www.cppreference.com/cppvector/erase.html

std::vector<int> v;
v.push_back(1);
v.push_back(2);

//Vector should contain the elements 1, 2

//Find new end iterator
std::vector<int>::iterator newEnd = std::remove(v.begin(), v.end(), 1);

//Erase the "removed" elements.
v.erase(newEnd, v.end());

//Vector should now only contain 2

Merci à Jim Buck d'avoir signalé mon erreur.

Bradtgmurray
la source
Cela a la capacité d'empêcher le mouvement d'autres éléments lors de l'effacement d'un élément au milieu du vecteur, c'est donc plus rapide. Il les déplace d'abord à la fin du vecteur, puis vous pouvez simplement les supprimer de la fin du vecteur.
Etherealone
5

Les autres réponses expliquent comment bien faire cela, mais j'ai pensé également souligner qu'il n'est pas vraiment étrange que ce ne soit pas dans l'API vectorielle: c'est une recherche inefficace et linéaire dans le vecteur de la valeur, suivie d'un tas de copier pour le supprimer.

Si vous effectuez cette opération de manière intensive, cela peut valoir la peine de considérer std :: set à la place pour cette raison.

Luke Halliwell
la source
5

Si vous avez un vecteur non trié, vous pouvez simplement échanger avec le dernier élément vectoriel resize().

Avec un conteneur commandé, vous serez mieux avec std::vector::erase(). Notez qu'il y a un std::remove()défini dans <algorithm>, mais cela ne fait pas réellement l'effacement. (Lisez attentivement la documentation).

nsanders
la source
3

À partir de C ++ 20 :

Une fonction non membre introduite std::erase, qui prend le vecteur et la valeur à supprimer comme entrées.

ex:

std::vector<int> v = {90,80,70,60,50};
std::erase(v,50);
Pavan Chandaka
la source
2
Non seulement cela, il est surchargé pour chaque conteneur spécifique!
HolyBlackCat
... et tire parti des propriétés spécifiques aux conteneurs, comme map::erase!
LF
2

Voir aussi std :: remove_if pour pouvoir utiliser un prédicat ...

Voici l'exemple du lien ci-dessus:

vector<int> V;
V.push_back(1);
V.push_back(4);
V.push_back(2);
V.push_back(8);
V.push_back(5);
V.push_back(7);

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 4 2 8 5 7"

vector<int>::iterator new_end = 
    remove_if(V.begin(), V.end(), 
              compose1(bind2nd(equal_to<int>(), 0),
                       bind2nd(modulus<int>(), 2)));
V.erase(new_end, V.end()); [1]

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 5 7".
Xavier Nodet
la source
2
Veuillez ajouter plus de détails à ce message. Dans l'état actuel des choses, la majorité de son contenu provient d'un lien et serait perdu si le lien se rompait un jour.
Mick MacCallum
qu'en est-il du fait que bind2nd est - (obsolète en C ++ 11) (supprimé en C ++ 17)
Idan Banani
0

Si vous voulez le faire sans aucun supplément comprend:

vector<IComponent*> myComponents; //assume it has items in it already.
void RemoveComponent(IComponent* componentToRemove)
{
    IComponent* juggler;

    if (componentToRemove != NULL)
    {
        for (int currComponentIndex = 0; currComponentIndex < myComponents.size(); currComponentIndex++)
        {
            if (componentToRemove == myComponents[currComponentIndex])
            {
                //Since we don't care about order, swap with the last element, then delete it.
                juggler = myComponents[currComponentIndex];
                myComponents[currComponentIndex] = myComponents[myComponents.size() - 1];
                myComponents[myComponents.size() - 1] = juggler;

                //Remove it from memory and let the vector know too.
                myComponents.pop_back();
                delete juggler;
            }
        }
    }
}
Katianie
la source
0

Vous pouvez utiliser deux méthodes pour effacer un élément en particulier. prenons un vecteur

std :: vector < int > v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(40);
v.push_back(50);

1) Manière non efficace: bien que cela semble assez efficace mais ce n'est pas parce que la fonction d'effacement supprime les éléments et décale tous les éléments vers la gauche de 1. donc sa complexité sera O (n ^ 2)

std :: vector < int > :: iterator itr = v.begin();
int value = 40;
while ( itr != v.end() )
{
   if(*itr == value)
   { 
      v.erase(itr);
   }
   else
       ++itr;
}

2) Moyen efficace (RECOMMANDÉ) : Il est également connu sous le nom de ERASE - REMOVE idioms .

  • std :: remove transforme la plage donnée en une plage avec tous les éléments qui se comparent non égaux à l'élément donné décalés au début du conteneur.
  • Donc, ne supprimez pas les éléments correspondants. Il a simplement déplacé le non apparié au début et donne un itérateur à une nouvelle fin valide. Cela nécessite juste une complexité O (n).

la sortie de l'algorithme de suppression est:

10 20 30 50 40 50 

comme type de retour de remove est un itérateur vers la nouvelle extrémité de cette plage.

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

Utilisez maintenant la fonction d'effacement du vecteur pour supprimer des éléments de la nouvelle extrémité à l'ancienne extrémité du vecteur. Il faut un temps O (1).

v.erase ( std :: remove (v.begin() , v.end() , element ) , v.end () );

donc cette méthode fonctionne en O (n)

Praveen Kumar
la source
0

*

La communauté C ++ a entendu votre demande :)

*

C ++ 20 fournit un moyen facile de le faire maintenant. Cela devient aussi simple que:

#include <vector>
...
vector<int> cnt{5, 0, 2, 8, 0, 7};
std::erase(cnt, 0);

Vous devriez vérifier std :: erase et std :: erase_if .

Non seulement il supprimera tous les éléments de la valeur (ici '0'), mais il le fera avec une complexité de temps O (n) . Quel est le meilleur que vous puissiez obtenir.

Si votre compilateur ne prend pas en charge C ++ 20, vous devez utiliser l' idiome erase-remove :

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());
Harshad Sharma
la source