121

I want to clear a element from a vector using the erase method. But the problem here is that the element is not guaranteed to occur only once in the vector. It may be present multiple times and I need to clear all of them. My code is something like this:

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

This code obviously crashes because I am changing the end of the vector while iterating through it. What is the best way to achieve this? I.e. is there any way to do this without iterating through the vector multiple times or creating one more copy of the vector?

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
Naveen
  • 74,600
  • 47
  • 176
  • 233

7 Answers7

201

Use the remove/erase idiom:

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

What happens is that remove compacts the elements that differ from the value to be removed (number_in) in the beginning of the vector and returns the iterator to the first element after that range. Then erase removes these elements (whose value is unspecified).


Edit: While updating a dead link I discovered that starting in C++20 there are freestanding std::erase and std::erase_if functions that work on containers and simplify things considerably.

Motti
  • 110,860
  • 49
  • 189
  • 262
  • 3
    `std::remove()` shifts elements such that the elements to remove are overwritten. The algorithm doesn't change the size of the container, and if `n` elements are removed then it is undefined what are the last `n` elements. – wilhelmtell Feb 14 '11 at 15:51
  • 22
    erase-remove idiom is described in Item 32 in the book "Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library" by Scott Meyers. – Alessandro Jacopson Jun 01 '11 at 18:47
  • How might I update this to remove a custom object, rather than a primitive? I'd like to delete something while iterating through a vector. – Benjin Jan 08 '13 at 01:43
  • 1
    @Benjin no update is needed, it will call the objects' destructors if they exist. – Motti Jan 08 '13 at 17:51
  • 77
    STL 'idioms' like this make me use Python for small projects. – Johannes Overmann Jun 25 '13 at 15:28
  • @Alessandro what makes you say that? – TankorSmash Dec 04 '14 at 23:39
  • Does it work correctly when the vector does not contain any elements with the specified value? – Violet Giraffe Mar 03 '15 at 14:13
  • @violetgiraffe yes, `remove` will return `end` and then `erase` will do nothing. – Motti Mar 03 '15 at 15:10
  • @Motti That contradicts [cppreference](http://en.cppreference.com/w/cpp/container/vector/erase), which says: __The iterator pos must be valid and dereferenceable. Thus the end() iterator (which is valid, but is not dereferencable) cannot be used as a value for pos.__ – Louis Dionne Mar 09 '17 at 23:38
  • 2
    @LouisDionne that refers to the one iterator overload, I'm using the two iterator overload – Motti Mar 13 '17 at 07:26
  • @Motti That's not true. The standard specifies that removing the end() is undefined behaviour. I verified that using Visual Studio 15.6.7 (which comes with its own STL implementation) in debug mode, remove throws an error when given vec.end(). – TamaMcGlinn May 27 '18 at 20:20
  • @VioletGiraffe don't remove(vec.end()) that's UB! – TamaMcGlinn May 27 '18 at 20:20
  • 3
    @TamaMcGlinn, this code doesn't remove `end()` it removes the range between `begin()` and `end()`. If `begin()` is equal to `end()` there are zero elements in the range and nothing is removed (ditto for `erase`). – Motti May 28 '18 at 06:31
  • 4
    Dear C++ Committees: what was wrong with std::vector.remove(T&v); (etc) ???!!! It's not like this is an uncommon use-case! 30-year c++ veteran coming back from C#/Java land after a five year hiatus. When exactly did this monstrosity happen, and where do I need to start reading to understand what happened to C++? – Robin Davies Feb 08 '21 at 15:54
  • As of Dec 17, 2022, the link referenced is broken. – Saurab Dulal Dec 17 '22 at 13:14
66

Calling erase will invalidate iterators, you could use:

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

}

Or you could use std::remove_if together with a functor and 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());

Instead of writing your own functor in this case you could use std::remove:

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

In C++11 you could use a lambda instead of a functor:

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 std::experimental::erase and std::experimental::erase_if are also available, in C++20 these are (finally) renamed to std::erase and std::erase_if (note: in Visual Studio 2019 you'll need to change your C++ language version to the latest experimental version for support):

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

or:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);
AStopher
  • 4,207
  • 11
  • 50
  • 75
dalle
  • 18,057
  • 5
  • 57
  • 81
15
  1. You can iterate using the index access,

  2. To avoid O(n^2) complexity you can use two indices, i - current testing index, j - index to store next item and at the end of the cycle new size of the vector.

code:

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 such case you have no invalidating of iterators, complexity is O(n), and code is very concise and you don't need to write some helper classes, although in some case using helper classes can benefit in more flexible code.

This code does not use erase method, but solves your task.

Using pure stl you can do this in the following way (this is similar to the Motti's answer):

#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());
}
Patryk
  • 22,602
  • 44
  • 128
  • 244
sergtk
  • 10,714
  • 15
  • 75
  • 130
4

Depending on why you are doing this, using a std::set might be a better idea than std::vector.

It allows each element to occur only once. If you add it multiple times, there will only be one instance to erase anyway. This will make the erase operation trivial. The erase operation will also have lower time complexity than on the vector, however, adding elements is slower on the set so it might not be much of an advantage.

This of course won't work if you are interested in how many times an element has been added to your vector or the order the elements were added.

Laserallan
  • 11,072
  • 10
  • 46
  • 67
4

There are std::erase and std::erase_if since C++20 which combines the remove-erase idiom.

std::vector<int> nums;
...
std::erase(nums, targetNumber);

or

std::vector<int> nums;
...
std::erase_if(nums, [](int x) { return x % 2 == 0; }); 
Eduard Rostomyan
  • 7,050
  • 2
  • 37
  • 76
1

If you change your code as follows, you can do stable deletion.

void atest(vector<int>& container,int number_in){
for (auto it = container.begin(); it != container.end();) {
    if (*it == number_in) {
        it = container.erase(it);
    } else {
        ++it;
    }
  }
}

However, a method such as the following can also be used.

void btest(vector<int>& container,int number_in){
   container.erase(std::remove(container.begin(), container.end(), number_in),container.end());
}

If we must preserve our sequence’s order (say, if we’re keeping it sorted by some interesting property), then we can use one of the above. But if the sequence is just a bag of values whose order we don’t care about at all, then we might consider moving single elements from the end of the sequence to fill each new gap as it’s created:

void ctest(vector<int>& container,int number_in){
  for (auto it = container.begin(); it != container.end(); ) {
   if (*it == number_in) {
     *it = std::move(container.back());
     container.pop_back();
   } else {
     ++it;
  }
 }
}

Below are their benchmark results: CLang 15.0: enter image description here

Gcc 12.2: enter image description here

kfc
  • 567
  • 1
  • 5
  • 10
0

You can use the find and consequently the erase method to remove a specific element.

For example:

auto ite = std::find(yourVector.begin(),yourVector.end(),element);
yourVector.erase(ite); 
cconsta1
  • 737
  • 1
  • 6
  • 20
Rohan
  • 11
  • 7