Jak usunąć element ze std :: vector <> według indeksu?


508

Mam std :: vector <int> i chcę usunąć n-ty element. Jak mogę to zrobić?

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

vec.erase(???);

4
Rozważ użycie std :: deque, które zapewnia wstawianie i usuwanie na obu końcach.
Dario

40
Nie, nie rozważaj używania deque tylko dlatego, że możesz chcieć usunąć element, to naprawdę kiepska rada. Istnieje wiele powodów, dla których warto użyć deque lub vector. Prawdą jest, że usunięcie elementu z wektora może być kosztowne - szczególnie jeśli wektor jest duży, ale nie ma powodu, aby sądzić, że deque byłby lepszy niż wektor z przykładu kodu, który właśnie opublikowałeś.
Sowa

6
Na przykład, jeśli masz aplikację graficzną, w której wyświetlasz „listę” rzeczy, w których wstawiasz / usuwasz rzeczy interaktywnie, rozważ przeglądanie listy 50–100 razy na sekundę, aby je wyświetlić, i dodajesz / usuwasz kilka rzeczy razy co minutę. Zatem wdrożenie „listy” jako wektora jest prawdopodobnie lepszym rozwiązaniem pod względem całkowitej wydajności.
Michel Billaud

Odpowiedzi:


705

Aby usunąć pojedynczy element, możesz:

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

// Deletes the second element (vec[1])
vec.erase(vec.begin() + 1);

Lub, aby usunąć więcej niż jeden element na raz:

// Deletes the second through third elements (vec[1], vec[2])
vec.erase(vec.begin() + 1, vec.begin() + 3);

50
Zauważ też, że binarne nieoperator+ jest koniecznie zdefiniowane dla iteratorów na innych typach kontenerów, takich jak (nie możesz tego zrobić na , musisz do tego użyć )list<T>::iteratorlist.begin() + 2std::liststd::advance
bobobobo

czy twierdzisz, że „+1” jest pierwszym elementem myVector [0] lub faktyczną pozycją myVector [1]
Karl Morrison

2
Z góry musisz zapisać iterator w zmiennej. Jeśli używasz std :: next, możesz to zrobić w jednym wierszu: vec.erase (next (begin (vec), 123));
dani

8
Dziękuję wszystkim, którzy odpowiedzieli. Co mamy myśleć o projekcie klasy, gdy tak prosta operacja, jak usunięcie elementu, wymaga przejścia do StackOverflow?
Pierre

5
@Pierre, ponieważ indeks liczbowy określonego elementu nie jest podstawowym modelem dostępu, iteratorem jest. Wszystkie funkcje, które patrzą na elementy kontenera, wykorzystują iteratory tego kontenera. Np.std::find_if
Caleth

212

Metoda kasowania na std :: vector jest przeciążona, więc prawdopodobnie łatwiej jest ją wywołać

vec.erase(vec.begin() + index);

gdy chcesz usunąć tylko jeden element.


3
Ale ten problem pojawia się bez względu na liczbę elementów.
Zyx 2000

15
jeśli jest tylko jeden element, indeks wynosi 0, więc dostajesz, vec.begin()który jest poprawny.
Anne Quinn

28
Chciałbym, żeby ktoś wspomniał, że vec.erase(0)to nie działa, ale vec.erase(vec.begin()+0)(lub bez +0) działa. W przeciwnym razie nie otrzymam pasującego wywołania funkcji, dlatego tu przyszedłem
qrtLs

@qrtLs vec.erase(0)może się właściwie skompilować, jeśli 0zostanie zinterpretowane jako stała zerowego wskaźnika ...
LF

57
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
    std::vector<T>::iterator it = vec.begin();
    std::advance(it, pos);
    vec.erase(it);
}

2
Max, co sprawia, że ​​ta funkcja jest lepsza niż: template <typename T> void remove(std::vector<T>& vec, size_t pos) { vec.erase(vec.begin + pos); }Nie twierdzę, że albo jest lepsza, po prostu pytam z osobistego zainteresowania i zwracam najlepszy wynik, jaki może uzyskać to pytanie.

13
@JoeyvG: Ponieważ a vector<T>::iteratorto iterator o dostępie swobodnym, twoja wersja jest w porządku i może nieco jaśniejsza. Ale wersja, którą opublikował Max, powinna działać dobrze, jeśli zmienisz kontener na inny, który nie obsługuje iteratorów o dostępie swobodnym
Lily Ballard,

2
To imo lepsza odpowiedź, ponieważ dotyczy również innych formatów kontenerów. Możesz także użyć std :: next ().
Bim

Znacznie lepsze podejście, ponieważ nie opiera się na elementach wewnętrznych pojemnika.
BartoszKP

std :: zaliczka jest potrzebna tylko, jeśli uważasz, że nie będzie to wektor, tj. lista. Ale jak już to określiłeś, czy operator + nie byłby prostszy? Według tego stackoverflow.com/questions/1668088/ ... możliwe jest zwiększenie wydajności dzięki operatorowi +
Neil McGill

14

eraseSposób zostaną wykorzystane na dwa sposoby:

  1. Kasowanie pojedynczego elementu:

    vector.erase( vector.begin() + 3 ); // Deleting the fourth element
  2. Kasowanie zakresu elementów:

    vector.erase( vector.begin() + 3, vector.begin() + 5 ); // Deleting from fourth element to sixth element

7
To jest duplikat odpowiedzi prawie 7 lat po zaakceptowanej odpowiedzi. Proszę nie rób tego.
AlastairG,

10

W rzeczywistości erasefunkcja działa dla dwóch profili:

  • Usuwanie pojedynczego elementu

    iterator erase (iterator position);
  • Usuwanie zakresu elementów

    iterator erase (iterator first, iterator last);

Ponieważ std :: vec.begin () oznacza początek kontenera i jeśli chcemy usunąć ity element z naszego wektora, możemy użyć:

vec.erase(vec.begin() + index);

Jeśli przyjrzysz się uważnie, vec.begin () jest tylko wskaźnikiem do pozycji początkowej naszego wektora, a dodanie do niego wartości i zwiększa wskaźnik do pozycji i, więc zamiast tego możemy uzyskać dostęp do wskaźnika do i-tego elementu poprzez:

&vec[i]

Więc możemy napisać:

vec.erase(&vec[i]); // To delete the ith element

6
-1 Ostatni wiersz się nie kompiluje (przynajmniej w VS2017). Kod zakłada, że ​​iterator vector :: jest domyślnie możliwy do zbudowania z surowego wskaźnika, który nie jest wymagany przez standard.
CuriousGeorge

1
Dotyczy to zwłaszcza iteratorów debugowania
Nishant Singh,

9

Jeśli masz nieuporządkowany wektor, możesz skorzystać z faktu, że jest nieuporządkowany i użyć czegoś, co widziałem od Dana Higginsa w CPPCON

template< typename TContainer >
static bool EraseFromUnorderedByIndex( TContainer& inContainer, size_t inIndex )
{
    if ( inIndex < inContainer.size() )
    {
        if ( inIndex != inContainer.size() - 1 )
            inContainer[inIndex] = inContainer.back();
        inContainer.pop_back();
        return true;
    }
    return false;
}

Ponieważ kolejność na liście nie ma znaczenia, po prostu weź ostatni element na liście i skopiuj go nad elementem, który chcesz usunąć, a następnie pop i usuń ostatni element.


Myślę, że to najlepsza odpowiedź, jeśli wektor jest nieuporządkowany. Nie opiera się na założeniu, że iterator + indexfaktycznie da ci pozycję iteratora przy tym indeksie, co nie jest prawdą dla wszystkich iterowalnych kontenerów. Jest to również stała złożoność zamiast liniowa dzięki wykorzystaniu tylnego wskaźnika.
theferrit32

1
To całkowicie należy dodać do standardowej biblioteki, ponieważ unordered_removei unordered_remove_if… chyba że tak było i ja tęskniłem, co dzieje się coraz częściej :)
Will Crawford

Jeśli sugeruje użycie przypisania przeniesienia lub zamiany zamiast przypisania kopii.
Carsten S

std::removezmienia kolejność pojemnika, tak aby wszystkie elementy, które mają zostać usunięte, znajdowały się na końcu, nie musisz robić tego ręcznie tak, jeśli używasz C ++ 17.
Keith

@keith jak std::removepomaga? cppreference twierdzi, że nawet w C ++ 17 wszystkie removeprzeciążenia wymagają predykatu i żaden nie przyjmuje indeksu.
Paul Du Bois

4

Jeśli pracujesz z dużymi wektorami (rozmiar> 100 000) i chcesz usunąć wiele elementów, polecam zrobić coś takiego:

int main(int argc, char** argv) {

    vector <int> vec;
    vector <int> vec2;

    for (int i = 0; i < 20000000; i++){
        vec.push_back(i);}

    for (int i = 0; i < vec.size(); i++)
    {
        if(vec.at(i) %3 != 0)
            vec2.push_back(i);
    }

    vec = vec2;
    cout << vec.size() << endl;
}

Kod pobiera wszystkie liczby w vec, których nie można podzielić przez 3, i kopiuje je do vec2. Następnie kopiuje vec2 do vec. To jest dość szybkie. Aby przetworzyć 20 000 000 elementów, ten algorytm zajmuje tylko 0,8 sekundy!

Zrobiłem to samo z metodą wymazywania i zajmuje to mnóstwo czasu:

Erase-Version (10k elements)  : 0.04 sec
Erase-Version (100k elements) : 0.6  sec
Erase-Version (1000k elements): 56   sec
Erase-Version (10000k elements): ...still calculating (>30 min)

6
jak to odpowiada na pytanie?
Regis Portalez

4
Ciekawe, ale nie związane z pytaniem!
Roddy,

Czy algorytm lokalny nie będzie szybszy?
user202729,

2
to std :: remove_if (+ erase)
RiaD,


3

Sugeruję przeczytać to, ponieważ uważam, że tego właśnie szukasz.https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom

Jeśli używasz na przykład

 vec.erase(vec.begin() + 1, vec.begin() + 3);

usuniesz n-ty element wektora, ale kiedy usuniesz drugi element, wszystkie inne elementy wektora zostaną przesunięte, a rozmiar wektora wyniesie -1. Może to stanowić problem, jeśli pętla przechodzi przez wektor, ponieważ rozmiar wektora () maleje. Jeśli masz taki problem, podany link sugeruje użycie istniejącego algorytmu w standardowej bibliotece C ++. i „remove” lub „remove_if”.

Mam nadzieję, że to pomogło


0

Poprzednie odpowiedzi zakładają, że zawsze masz podpisany indeks. Niestety, std::vectorużywa size_typedo indeksowania i difference_typearytmetyki iteratora, więc nie działają razem, jeśli masz włączoną opcję „-Wconversion” i znajomych. Jest to kolejny sposób na udzielenie odpowiedzi na pytanie, z możliwością obsługi zarówno podpisanych, jak i niepodpisanych:

Usuwać:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
void remove(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);
    v.erase(iter);
}

Brać:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
T take(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);

    auto val = *iter;
    v.erase(iter);

    return val;
}

0

oto jeszcze jeden sposób, aby to zrobić, jeśli chcesz usunąć element, znajdując go z jego wartością w wektorze, wystarczy to zrobić w wektorze.

vector<int> ar(n);
ar.erase(remove(ar.begin(), ar.end()), (place your value here from vector array));

usunie twoją wartość stąd. dzięki


0

Co powiesz na to?

void squeeze(vector<int> &v)
{
    int j = 0;
    for (int i = 1; i < v.size(); i++)
        if (v[i] != v[j] && ++j != i)
            v[j] = v[i];
    v.resize(j + 1);
}

-4

najszybszy sposób (dla programowania konkursów według złożoności czasowej () = stała)

może usunąć 100 mln pozycji w ciągu 1 sekundy;

    vector<int> it = (vector<int>::iterator) &vec[pos];
    vec.erase(it);

i najbardziej czytelny sposób: vec.erase(vec.begin() + pos);


2
To jest bardzo nieprzenośne; będzie działać z libstdc ++, ale nie z libc ++ i nie z MSVC. vector<int>::iteratorniekoniecznie jest taki sam jakint *
Marshall Clow

2
To obrzydliwe, myślę, że zmienię libstdc ++, żeby przestał działać.
Jonathan Wakely
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.