Wie entferne ich ein Element aus einem stl-Vektor mit einem bestimmten Wert?

145

Ich habe mir die API-Dokumentation für stl vector angesehen und festgestellt, dass es für die Vektorklasse keine Methode gibt, mit der ein Element mit einem bestimmten Wert entfernt werden kann. Dies scheint eine übliche Operation zu sein, und es scheint seltsam, dass es keine eingebaute Möglichkeit gibt, dies zu tun.

Bradtgmurray
quelle
2
Ich weiß, dass ich das schon mehrmals erwähnt habe, aber Scott Meyers Buch Effective STL behandelt diese Fallstricke auf klare Weise.
Rob Wells
1
Siehe auch
Bobobobo
Dies könnte eine interessante Lektüre für Sie sein: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
Sergiol

Antworten:

165

std::removelöscht das Element nicht tatsächlich aus dem Container, sondern gibt den neuen Enditerator zurück, an den übergeben werden kann, um container_type::erasedie zusätzlichen Elemente, die sich jetzt am Ende des Containers befinden, WIRKLICH zu entfernen:

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
quelle
3
Hängt dieses All-in-One-Anweisungsformular von der Reihenfolge ab, in der der Compiler die Argumente auswertet, oder ist vec.end()garantiert, dass es auf beiden Seiten des Aufrufs von gleich ist std::remove? Es scheint mir sicher zu sein, andere Teile des Webs zu lesen, aber es sollte klar angegeben werden.
dmckee --- Ex-Moderator Kätzchen
1
Das ist gut. Das Ergebnis von vec.end()muss nicht dasselbe sein. es muss nur richtig sein (was es ist).
Jim Buck
8
vec.end()muss gleich sein, aber das ist in Ordnung, weil std::removees nichts ändert. Wenn es geändert würde (und den alten Wert ungültig macht), würde es ein Problem geben: Die Reihenfolge der Auswertung der Parameter ist nicht spezifiziert und daher würden Sie nicht wissen, ob der zweite vec.end()zum Zeitpunkt seiner Verwendung noch gültig ist. Der Grund, warum es dasselbe ist, ist einfach: Es std::removeändert nicht die Größe des Containers, sondern verschiebt nur den Inhalt.
Steve Jessop
2
Ich fand diese Frage wichtig, da ich das gleiche Problem habe. Aber mein visuelles Studio nimmt std::removenur ein Argument an; das ist const char *_Filename. Welche Methode muss ich aufrufen?
Victor
15
Das ist die Version remove, die eine Datei löscht. Sie müssen einschließen, <algorithm>um auf die Version zuzugreifen, die removesich mit Containern befasst.
Jim Buck
64

Wenn Sie ein Element entfernen möchten , ist das Folgende etwas effizienter.

std::vector<int> v;


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

oder Sie können den Aufwand für das Verschieben der Artikel vermeiden, wenn die Bestellung für Sie keine Rolle spielt:

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();
}
Etherealone
quelle
3
Beachten Sie, dass dadurch keine Duplikate des Elements entfernt werden, wenn sie vorhanden sind, wohingegen der Ansatz std :: remove_if dies tut.
Den-Jason
15

Verwenden Sie die globale Methode std :: remove mit dem Start- und Enditerator und verwenden Sie dann std :: vector.erase, um die Elemente tatsächlich zu entfernen.

Dokumentationslinks
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

Vielen Dank an Jim Buck für den Hinweis auf meinen Fehler.

Bradtgmurray
quelle
Dies hat die Fähigkeit, die Bewegung anderer Elemente beim Löschen eines Elements in der Mitte des Vektors zu verhindern, daher ist dies schneller. Es verschiebt sie zuerst an das Ende des Vektors, dann können Sie sie einfach vom Ende des Vektors wegwerfen.
Etherealone
5

Die anderen Antworten behandeln, wie man das gut macht, aber ich dachte, ich möchte auch darauf hinweisen, dass es nicht wirklich seltsam ist, dass dies nicht in der Vektor-API enthalten ist: Es ist eine ineffiziente, lineare Suche durch den Vektor nach dem Wert, gefolgt von einer Reihe zu kopieren, um es zu entfernen.

Wenn Sie diesen Vorgang intensiv ausführen, kann es aus diesem Grund sinnvoll sein, stattdessen std :: set in Betracht zu ziehen.

Luke Halliwell
quelle
5

Wenn Sie einen unsortierten Vektor haben, können Sie dann einfach mit dem letzten Vektorelement tauschen resize().

Mit einem geordneten Behälter, werden Sie am besten dran sein mit std::vector::erase(). Beachten Sie, dass in std::remove()definiert ist <algorithm>, das Löschen jedoch nicht durchgeführt wird. (Lesen Sie die Dokumentation sorgfältig durch).

nsanders
quelle
3

Ab c ++ 20 :

Es wurde eine Nichtmitgliedsfunktion eingeführt std::erase, die den zu entfernenden Vektor und Wert als Eingaben verwendet.

Ex:

std::vector<int> v = {90,80,70,60,50};
std::erase(v,50);
Pavan Chandaka
quelle
2
Nicht nur das, es ist für jeden einzelnen Container überladen!
HolyBlackCat
... und nutzt container-spezifische Eigenschaften wie map::erase!
LF
2

Siehe auch std :: remove_if , um ein Prädikat verwenden zu können ...

Hier ist das Beispiel aus dem obigen Link:

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
quelle
2
Bitte fügen Sie diesem Beitrag weitere Details hinzu. Derzeit stammt ein Großteil des Inhalts von einem Link und würde verloren gehen, wenn der Link jemals unterbrochen wird.
Mick MacCallum
Was ist mit der Tatsache, dass bind2nd ist - (veraltet in C ++ 11) (entfernt in C ++ 17)
Idan Banani
0

Wenn Sie es ohne zusätzliche Funktionen tun möchten, gehören:

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
quelle
0

Es gibt zwei Möglichkeiten, mit denen Sie ein Element besonders löschen können. Nehmen wir einen Vektor

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) Nicht effizienter Weg: Obwohl es ziemlich effizient zu sein scheint, liegt es nicht daran, dass die Löschfunktion die Elemente löscht und alle Elemente um 1 nach links verschiebt, sodass ihre Komplexität O (n ^ 2) ist.

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

2) Effizienter Weg (EMPFOHLEN) : Es wird auch als ERASE - REMOVE-Redewendungen bezeichnet .

  • std :: remove transformiert den angegebenen Bereich in einen Bereich, in dem alle Elemente, die nicht mit dem angegebenen Element verglichen werden, an den Anfang des Containers verschoben werden.
  • Entfernen Sie also nicht die übereinstimmenden Elemente. Es hat nur das nicht übereinstimmende zum Start verschoben und gibt einen Iterator zum neuen gültigen Ende. Es erfordert nur O (n) Komplexität.

Die Ausgabe des Entfernungsalgorithmus lautet:

10 20 30 50 40 50 

Als Rückgabetyp entfernt wird der Iterator zum neuen Ende dieses Bereichs.

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

Verwenden Sie nun die Löschfunktion des Vektors, um Elemente vom neuen zum alten Ende des Vektors zu löschen. Es benötigt O (1) Zeit.

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

Diese Methode funktioniert also in O (n).

Praveen Kumar
quelle
0

* *

C ++ Community hat Ihre Anfrage gehört :)

* *

C ++ 20 bietet jetzt eine einfache Möglichkeit, dies zu tun. Es wird so einfach wie:

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

Sie sollten std :: erase und std :: erase_if auschecken .

Es werden nicht nur alle Elemente des Werts (hier '0') entfernt, sondern auch in O (n) -Zeitkomplexität. Welches ist das Beste, was Sie bekommen können.

Wenn Ihr Compiler C ++ 20 nicht unterstützt, sollten Sie das Erase-Remove-Idiom verwenden :

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