Come posso cancellare un elemento da std :: vector <> per indice?


508

Ho uno std :: vector <int> e voglio eliminare l'ennesimo elemento. Come lo faccio?

std::vector<int> vec;

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

vec.erase(???);

4
Prendi in considerazione l'utilizzo di uno std :: deque che fornisce l'inserimento e l'eliminazione su entrambe le estremità.
Dario,

40
No, non considerare l'utilizzo di deque solo perché potresti voler eliminare un elemento, è davvero un consiglio scadente. Ci sono molte ragioni per cui potresti voler usare deque o vector. È vero che cancellare un elemento da un vettore può essere costoso, specialmente se il vettore è grande, ma non c'è motivo di pensare che una deque sarebbe meglio di un vettore dall'esempio di codice che hai appena pubblicato.
Gufo

6
Ad esempio, se si dispone di un'applicazione grafica in cui si visualizza un "elenco" di elementi in cui si inseriscono / rimuovono elementi interattivamente, considerare di scorrere l'elenco 50-100 volte al secondo per visualizzarli e aggiungere / rimuovere elementi alcuni volte al minuto. Quindi implementare la "lista" come vettore è probabilmente un'opzione migliore in termini di efficienza totale.
Michel Billaud,

Risposte:


706

Per eliminare un singolo elemento, puoi fare:

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);

Oppure, per eliminare più di un elemento contemporaneamente:

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

50
Nota anche che il binario nonoperator+ è necessariamente definito per gli iteratori su altri tipi di container, come (non puoi farlo su un , devi usarlo per quello)list<T>::iteratorlist.begin() + 2std::liststd::advance
bobobobo

stai affermando che "+1" è il primo elemento myVector [0] o la posizione effettiva myVector [1]
K - La tossicità in SO sta crescendo.

2
Con anticipo è necessario salvare l'iteratore in una variabile. Se usi std :: next puoi farlo in una riga: vec.erase (next (begin (vec), 123));
dani,

8
Grazie a tutti coloro che hanno risposto. Cosa dobbiamo pensare di un design di classe quando un'operazione così semplice come l'eliminazione di un elemento, richiede uno per arrivare a StackOverflow?
Pierre,

5
@Pierre perché l' indice numerico di un particolare elemento non è il modello principale di accesso, lo è iteratore . Tutte le funzioni che osservano gli elementi di un contenitore utilizzano gli iteratori di quel contenitore. Ad esempiostd::find_if
Caleth,

212

Il metodo di cancellazione su std :: vector è sovraccarico, quindi è probabilmente più chiaro chiamare

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

quando vuoi solo cancellare un singolo elemento.


3
Ma quel problema appare non importa quanti elementi hai.
Zyx 2000,

15
se c'è un solo elemento, l'indice è 0 e quindi ottieni vec.begin()quale è valido.
Anne Quinn,

28
Vorrei che qualcuno avrebbe detto che vec.erase(0)non funziona, ma vec.erase(vec.begin()+0)(o senza +0) funziona. Altrimenti non ricevo alcuna chiamata di funzione corrispondente, motivo per cui sono venuto qui
qrtLs

@qrtLs vec.erase(0)può effettivamente compilare se 0viene interpretato come costante del puntatore null ...
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, ciò che rende questa funzione migliore di: template <typename T> void remove(std::vector<T>& vec, size_t pos) { vec.erase(vec.begin + pos); }non sto dicendo che sia meglio, semplicemente chiedendo per interesse personale e per restituire il miglior risultato che questa domanda potrebbe ottenere.

13
@JoeyvG: Dal momento che a vector<T>::iteratorè un iteratore ad accesso casuale, la tua versione va bene e forse un po 'più chiara. Ma la versione pubblicata da Max dovrebbe funzionare bene se si cambia il contenitore in un altro che non supporta iteratori ad accesso casuale
Lily Ballard,

2
Questa è la risposta migliore, poiché si applica anche ad altri formati di container. Puoi anche usare std :: next ().
Bim

Approccio molto migliore poiché non si basa sugli interni del contenitore.
BartoszKP,

std :: anticipo è necessario solo se pensi che questo non sarà un vettore, cioè un elenco. Ma come hai specificato qui, l'operatore + non sarebbe più semplice? Secondo questo stackoverflow.com/questions/1668088/… c'è un possibile guadagno in termini di prestazioni con l'operatore +
Neil McGill

14

Il erasemetodo verrà utilizzato in due modi:

  1. Cancellazione di un singolo elemento:

    vector.erase( vector.begin() + 3 ); // Deleting the fourth element
  2. Cancellazione della gamma di elementi:

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

7
Questa è una risposta duplicata quasi 7 anni dopo la risposta accettata. Per favore, non farlo.
AlastairG,

10

In realtà, la erasefunzione funziona per due profili:

  • Rimozione di un singolo elemento

    iterator erase (iterator position);
  • Rimozione di una serie di elementi

    iterator erase (iterator first, iterator last);

Poiché std :: vec.begin () segna l'inizio del contenitore e se vogliamo eliminare l'elemento ith nel nostro vettore, possiamo usare:

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

Se guardi da vicino, vec.begin () è solo un puntatore alla posizione iniziale del nostro vettore e l'aggiunta del valore di i ad esso incrementa il puntatore alla posizione i, quindi invece possiamo accedere al puntatore all'elemento ith da:

&vec[i]

Quindi possiamo scrivere:

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

6
-1 L'ultima riga non viene compilata (almeno in VS2017). Il codice presuppone che vector :: iterator sia implicitamente costruibile da un puntatore non elaborato, che non è richiesto dallo standard.
CuriousGeorge,

1
Questo è vero soprattutto per gli iteratori di debug
Nishant Singh

9

Se hai un vettore non ordinato, puoi trarre vantaggio dal fatto che è non ordinato e utilizzare qualcosa che ho visto da Dan Higgins a 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;
}

Poiché l'ordine dell'elenco non ha importanza, basta prendere l'ultimo elemento nell'elenco e copiarlo nella parte superiore dell'elemento che si desidera rimuovere, quindi pop ed eliminare l'ultimo elemento.


Penso che questa sia la risposta migliore se il vettore non è ordinato. Non si basa sul presupposto che iterator + indexti restituirà effettivamente la posizione dell'iteratore in quell'indice, il che non è vero per tutti i contenitori iterabili. È anche una complessità costante anziché lineare sfruttando il puntatore posteriore.
theferrit32

1
Questo deve assolutamente essere aggiunto alla std lib come unordered_removee unordered_remove_if... a meno che non sia stato e me lo sono perso, cosa che sta accadendo sempre più spesso in questi giorni :)
Will Crawford,

Se suggerirei di utilizzare l'assegnazione di spostamento o lo scambio invece di un'assegnazione di copia.
Carsten S

std::removeriordina il contenitore in modo che tutti gli elementi da rimuovere siano alla fine, non è necessario farlo manualmente in questo modo se si utilizza C ++ 17.
keith

@keith come std::removeaiuta? cppreference afferma che anche in C ++ 17, tutti i removesovraccarichi richiedono un predicato e nessuno accetta un indice.
Paul Du Bois,

4

Se lavori con vettori di grandi dimensioni (dimensioni> 100.000) e desideri eliminare molti elementi, ti consiglio di fare qualcosa del genere:

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;
}

Il codice prende ogni numero in vec che non può essere diviso per 3 e lo copia in vec2. Successivamente copia vec2 in vec. È abbastanza veloce Per elaborare 20.000.000 di elementi, questo algoritmo richiede solo 0,8 secondi!

Ho fatto la stessa cosa con il metodo di cancellazione e ci vuole un sacco di tempo:

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
come risponde alla domanda?
Regis Portalez,

4
Interessante, ma non pertinente alla domanda!
Roddy,

Un algoritmo sul posto non sarà più veloce?
user202729

2
quello è std :: remove_if (+ erase)
RiaD


3

Suggerisco di leggere questo poiché credo che sia quello che stai cercando. https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom

Se usi ad esempio

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

cancellerai l'ennesimo elemento del vettore ma quando cancellerai il secondo elemento, tutti gli altri elementi del vettore verranno spostati e le dimensioni del vettore saranno -1. Questo può essere un problema se si passa in rassegna il vettore poiché la dimensione del vettore () sta diminuendo. Se hai problemi come questo link fornito suggerito di utilizzare l'algoritmo esistente nella libreria C ++ standard. e "rimuovi" o "rimuovi_if".

Spero che questo abbia aiutato


0

Le risposte precedenti presuppongono che tu abbia sempre un indice firmato. Purtroppo, std::vectorusa size_typeper l'indicizzazione e difference_typeper l'aritmetica dell'iteratore, quindi non funzionano insieme se hai abilitato "-Conversione" e gli amici. Questo è un altro modo di rispondere alla domanda, pur essendo in grado di gestire sia firmati che non firmati:

Rimuovere:

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);
}

Prendere:

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

ecco un altro modo per farlo se vuoi eliminare un elemento trovandolo con il suo valore nel vettore, devi solo farlo sul vettore.

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

rimuoverà il tuo valore da qui. Grazie


0

Cosa ne pensi di questo?

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

il modo più veloce (per i concorsi di programmazione per complessità temporale () = costante)

può cancellare 100M di elemento in 1 secondo;

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

e il modo più leggibile: vec.erase(vec.begin() + pos);


2
Questo è molto non portatile; funzionerà con libstdc ++, ma non con libc ++ e non con MSVC. vector<int>::iteratornon è necessariamente lo stesso diint *
Marshall Clow,

2
È disgustoso, penso che cambierò libstdc ++ per impedirgli di funzionare.
Jonathan Wakely, il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.