Kasowanie elementów z wektora

101

Chcę usunąć element z wektora za pomocą metody wymazywania. Ale problem polega na tym, że nie ma gwarancji, że element wystąpi tylko raz w wektorze. Może występować wiele razy i muszę je wszystkie usunąć. Mój kod wygląda mniej więcej tak:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    std::vector<int>::iterator endIter = myNumbers_in.end();
    for(; iter != endIter; ++iter)
    {
        if(*iter == number_in)
        {
            myNumbers_in.erase(iter);
        }
    }
}

int main(int argc, char* argv[])
{
    std::vector<int> myNmbers;
    for(int i = 0; i < 2; ++i)
    {
        myNmbers.push_back(i);
        myNmbers.push_back(i);
    }

    erase(myNmbers, 1);

    return 0;
}

Ten kod oczywiście ulega awarii, ponieważ podczas iteracji zmieniam koniec wektora. Jaki jest najlepszy sposób, aby to osiągnąć? Czy jest jakiś sposób, aby to zrobić bez wielokrotnego iterowania wektora lub tworzenia jeszcze jednej kopii wektora?

Naveen
źródło

Odpowiedzi:

167

Użyj idiomu remove / erase :

std::vector<int>& vec = myNumbers; // use shorter name
vec.erase(std::remove(vec.begin(), vec.end(), number_in), vec.end());

Dzieje się tak, że removekompaktuje elementy, które różnią się od wartości do usunięcia ( number_in) na początku vectori zwraca iterator do pierwszego elementu po tym zakresie. Następnie eraseusuwa te elementy (których wartość jest nieokreślona).

Motti
źródło
2
std::remove()przesuwa elementy w taki sposób, że elementy do usunięcia są nadpisywane. Algorytm nie zmienia rozmiaru kontenera, a jeśli nelementy zostaną usunięte, nie jest zdefiniowane, jakie są ostatnie nelementy.
wilhelmtell,
20
Idiom erase-remove jest opisany w pozycji 32 w książce „Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library” autorstwa Scotta Meyersa.
Alessandro Jacopson
1
@Benjin nie jest wymagana żadna aktualizacja, wywoła destruktory obiektów, jeśli one istnieją.
Motti,
54
Takie „idiomy” STL sprawiają, że używam Pythona w małych projektach.
Johannes Overmann
1
@LouisDionne, który odnosi się do przeciążenia jednego iteratora, używam przeciążenia dwóch iteratorów
Motti
53

Wywołanie kasowania unieważni iteratory, możesz użyć:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    while (iter != myNumbers_in.end())
    {
        if (*iter == number_in)
        {
            iter = myNumbers_in.erase(iter);
        }
        else
        {
           ++iter;
        }
    }

}

Lub możesz użyć std :: remove_if razem z funktorem i std :: vector :: erase:

struct Eraser
{
    Eraser(int number_in) : number_in(number_in) {}
    int number_in;
    bool operator()(int i) const
    {
        return i == number_in;
    }
};

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), Eraser(number_in)), myNumbers.end());

Zamiast pisać własny funktor w tym przypadku możesz użyć std :: remove :

std::vector<int> myNumbers;
myNumbers.erase(std::remove(myNumbers.begin(), myNumbers.end(), number_in), myNumbers.end());

W C ++ 11 zamiast funktora można by użyć lambdy:

std::vector<int> myNumbers;
myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), [number_in](int number){ return number == number_in; }), myNumbers.end());

W C ++ 17 dostępne są również std :: experimental :: erase i std :: experimental :: erase_if , w C ++ 20 są one (ostatecznie) przemianowane na std :: erase i std :: erase_if :

std::vector<int> myNumbers;
std::erase_if(myNumbers, Eraser(number_in)); // or use lambda

lub:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);
dalle
źródło
2
Po co używać własnego funktora, skoro można użyć equal_to? :-P sgi.com/tech/stl/equal_to.html
Chris Jester-Young,
3
Nawiasem mówiąc, dzwonienie za erasepomocą removejest kanonicznym sposobem na zrobienie tego.
Konrad Rudolph,
1
Myślę, że on to robi. ale powinien użyć remove_if jeśli używa własnego funktora iirc. lub po prostu użyj usuń bez funktora
Johannes Schaub - litb
3
+1 Podany kod właśnie pomógł mi w konkursie programistycznym, podczas gdy „po prostu użyj idiomu remove-erase” nie.
13
  1. Możesz iterować używając dostępu do indeksu,

  2. Aby uniknąć złożoności O (n ^ 2), możesz użyć dwóch indeksów, i - bieżącego indeksu testowego, j - indeksu do przechowywania następnego elementu i na koniec cyklu nowego rozmiaru wektora.

kod:

void erase(std::vector<int>& v, int num)
{
  size_t j = 0;
  for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] != num) v[j++] = v[i];
  }
  // trim vector to new size
  v.resize(j);
}

W takim przypadku nie masz unieważniania iteratorów, złożoność wynosi O (n), a kod jest bardzo zwięzły i nie musisz pisać niektórych klas pomocniczych, chociaż w niektórych przypadkach użycie klas pomocniczych może przynieść korzyści w bardziej elastycznym kodzie.

Ten kod nie używa erase metody, ale rozwiązuje twoje zadanie.

Używając czystego stl możesz to zrobić w następujący sposób (jest to podobne do odpowiedzi Mottiego):

#include <algorithm>

void erase(std::vector<int>& v, int num) {
    vector<int>::iterator it = remove(v.begin(), v.end(), num);
    v.erase(it, v.end());
}
sergtk
źródło
4

W zależności od tego, dlaczego to robisz, używając std :: set może być lepszym pomysłem niż std :: vector.

Dzięki temu każdy element występuje tylko raz. Jeśli dodasz go wiele razy, i tak będzie tylko jedno wystąpienie do usunięcia. To sprawi, że operacja wymazywania będzie banalna. Operacja wymazywania będzie również miała mniejszą złożoność czasową niż na wektorze, jednak dodawanie elementów na planie jest wolniejsze, więc może nie być zbyt korzystne.

To oczywiście nie zadziała, jeśli interesuje Cię, ile razy element został dodany do twojego wektora lub kolejność dodawania elementów.

Laserallan
źródło
-1

Aby usunąć pierwszy element, możesz użyć:

vector<int> mV{ 1, 2, 3, 4, 5 }; 
vector<int>::iterator it; 

it = mV.begin(); 
mV.erase(it); 
Amit Vujic
źródło
1
To nie było pytanie. Twoja odpowiedź po 11 latach nie wydaje się trafna, Amit!
Asteroids With Wings