Apagando elementos de um vetor

101

Quero limpar um elemento de um vetor usando o método erase. Mas o problema aqui é que não é garantido que o elemento ocorra apenas uma vez no vetor. Pode estar presente várias vezes e preciso limpar todos eles. Meu código é mais ou menos assim:

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

Esse código obviamente falha porque estou alterando o final do vetor enquanto itero por ele. Qual a melhor maneira de alcançar isto? Ou seja, há alguma maneira de fazer isso sem iterar o vetor várias vezes ou criar mais uma cópia do vetor?

Naveen
fonte

Respostas:

167

Use o idioma remover / apagar :

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

O que acontece é que removecompacta os elementos que diferem do valor a ser removido ( number_in) no início do vectore retorna o iterador para o primeiro elemento após esse intervalo. Em seguida, eraseremove esses elementos (cujo valor não é especificado).

Motti
fonte
2
std::remove()desloca elementos de forma que os elementos a serem removidos sejam sobrescritos. O algoritmo não muda o tamanho do container, e se os nelementos são removidos então fica indefinido quais são os últimos nelementos.
wilhelmtell
20
O idioma apagar-remover é descrito no Item 32 do livro "STL efetivo: 50 maneiras específicas de melhorar o uso da biblioteca de modelos padrão", de Scott Meyers.
Alessandro Jacopson
1
@Benjin nenhuma atualização é necessária, ele irá chamar os destruidores dos objetos se eles existirem.
Motti
54
'Expressões' STL como este me fazem usar Python para pequenos projetos.
Johannes Overmann
1
@LouisDionne que se refere à sobrecarga de um iterador, estou usando a sobrecarga de dois iteradores
Motti
53

Chamar o apagamento invalidará os iteradores, você pode usar:

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

}

Ou você pode usar std :: remove_if junto com um functor 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());

Em vez de escrever seu próprio functor, neste caso, você pode usar std :: remove :

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

No C ++ 11, você pode usar um lambda em vez de um functor:

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

Em C ++ 17 std :: experimental :: erase e std :: experimental :: erase_if também estão disponíveis, em C ++ 20 eles são (finalmente) renomeados para std :: erase e std :: erase_if :

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

ou:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);
Dalle
fonte
2
Por que usar seu próprio functor quando você pode usar equal_to? :-P sgi.com/tech/stl/equal_to.html
Chris Jester-Young
3
A propósito, chamar erasecom removeé a maneira canônica de fazer isso.
Konrad Rudolph
1
acho que ele faz exatamente isso. mas ele deve usar remove_if se estiver usando um iirc de functor próprio. ou apenas use remove sem o functor
Johannes Schaub - litb
3
+1 O código soletrado apenas me ajudou em uma competição de programação, enquanto "apenas use o idioma remover-apagar" não.
13
  1. Você pode iterar usando o acesso ao índice,

  2. Para evitar a complexidade O (n ^ 2), você pode usar dois índices, i - índice de teste atual, j - índice para armazenar o próximo item e no final do ciclo um novo tamanho do vetor.

código:

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

Nesse caso, você não tem invalidação de iteradores, a complexidade é O (n) e o código é muito conciso e você não precisa escrever algumas classes auxiliares, embora em alguns casos o uso de classes auxiliares possa se beneficiar com um código mais flexível.

Este código não usa erasemétodo, mas resolve sua tarefa.

Usando STL puro, você pode fazer isso da seguinte maneira (isso é semelhante à resposta do 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());
}
sergtk
fonte
4

Dependendo de porque você está fazendo isso, usar um std :: set pode ser uma ideia melhor do que std :: vector.

Ele permite que cada elemento ocorra apenas uma vez. Se você adicioná-lo várias vezes, haverá apenas uma instância para apagar de qualquer maneira. Isso tornará a operação de apagamento trivial. A operação de apagamento também terá menor complexidade de tempo do que no vetor; no entanto, adicionar elementos é mais lento no conjunto, portanto, pode não ser uma grande vantagem.

É claro que isso não funcionará se você estiver interessado em quantas vezes um elemento foi adicionado ao seu vetor ou a ordem em que os elementos foram adicionados.

Laserallan
fonte
-1

Para apagar o primeiro elemento, você pode usar:

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

it = mV.begin(); 
mV.erase(it); 
Amit Vujic
fonte
1
Essa não era a questão. Sua resposta 11 anos depois não parece relevante, Amit!
Asteroids With Wings