Cancellazione di elementi da un vettore


101

Voglio cancellare un elemento da un vettore usando il metodo di cancellazione. Ma il problema qui è che non è garantito che l'elemento si presenti solo una volta nel vettore. Potrebbe essere presente più volte e devo cancellarle tutte. Il mio codice è qualcosa del genere:

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

Questo codice ovviamente si arresta in modo anomalo perché sto cambiando la fine del vettore durante l'iterazione. Qual è il modo migliore per ottenere questo risultato? Esiste un modo per farlo senza iterare più volte nel vettore o creare un'altra copia del vettore?

Risposte:


167

Usa l' idioma rimuovi / cancella :

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

Quello che succede è che removecompatta gli elementi che differiscono dal valore da rimuovere ( number_in) all'inizio del vectore restituisce l'iteratore al primo elemento dopo quell'intervallo. Quindi eraserimuove questi elementi (il cui valore non è specificato).


2
std::remove()sposta gli elementi in modo tale che gli elementi da rimuovere vengano sovrascritti. L'algoritmo non cambia la dimensione del contenitore e se gli nelementi vengono rimossi non è definito quali sono gli ultimi nelementi.
Wilhelmtell

20
L'idioma cancella-rimuovi è descritto nell'elemento 32 del libro "Efficace STL: 50 modi specifici per migliorare l'uso della libreria di modelli standard" di Scott Meyers.
Alessandro Jacopson

1
@Benjin non è necessario alcun aggiornamento, chiamerà i distruttori degli oggetti se esistono.
Motti

54
Gli "idiomi" STL come questo mi fanno usare Python per piccoli progetti.
Johannes Overmann

1
@LouisDionne che si riferisce al sovraccarico di un iteratore, sto usando il sovraccarico di due iteratori
Motti

53

La chiamata di cancellazione invaliderà gli iteratori, potresti usare:

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

}

Oppure potresti usare std :: remove_if insieme a un funtore e 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());

Invece di scrivere il tuo funtore in questo caso potresti usare std :: remove :

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

In C ++ 11 potresti usare un lambda invece di un funtore:

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

In C ++ 17 sono disponibili anche std :: experiment :: erase e std :: experiment :: erase_if , in C ++ 20 questi sono (finalmente) rinominati in std :: erase e std :: erase_if :

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

o:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);

2
Perché usare il tuo funtore quando puoi usare equal_to? :-P sgi.com/tech/stl/equal_to.html
Chris Jester-Young

3
A proposito, chiamare erasecon removeè il modo canonico per farlo.
Konrad Rudolph,

1
Penso che faccia esattamente questo. ma dovrebbe usare remove_if se usa un proprio funtore iirc. o semplicemente usa remove senza il funtore
Johannes Schaub - litb

3
+1 Il codice enunciato mi ha appena aiutato in una competizione di programmazione, mentre "usa solo l'idioma rimuovi-cancella" no.

13
  1. Puoi iterare utilizzando l'accesso all'indice,

  2. Per evitare la complessità O (n ^ 2) è possibile utilizzare due indici, i - indice di prova corrente, j - indice per memorizzare l'elemento successivo e alla fine del ciclo una nuova dimensione del vettore.

codice:

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

In tal caso non hai invalidazione degli iteratori, la complessità è O (n) e il codice è molto conciso e non è necessario scrivere alcune classi helper, sebbene in alcuni casi l'utilizzo di classi helper possa trarre vantaggio da un codice più flessibile.

Questo codice non usa il erasemetodo, ma risolve il tuo compito.

Usando pure stl puoi farlo nel modo seguente (questo è simile alla risposta di Motti):

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

4

A seconda del motivo per cui lo stai facendo, usare uno std :: set potrebbe essere un'idea migliore di std :: vector.

Consente a ciascun elemento di verificarsi una sola volta. Se lo aggiungi più volte, ci sarà comunque solo un'istanza da cancellare. Ciò renderà l'operazione di cancellazione banale. L'operazione di cancellazione avrà anche una complessità temporale inferiore rispetto al vettore, tuttavia, l'aggiunta di elementi è più lenta sul set, quindi potrebbe non essere un grande vantaggio.

Questo ovviamente non funzionerà se sei interessato a quante volte un elemento è stato aggiunto al tuo vettore o all'ordine in cui gli elementi sono stati aggiunti.


-1

Per cancellare il 1 ° elemento puoi usare:

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

it = mV.begin(); 
mV.erase(it); 

1
Non era questa la domanda. La tua risposta di 11 anni dopo non sembra pertinente, Amit!
Asteroids With Wings
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.