23
votes

Here is my issue, lets say I have a std::vector with ints in it.

let's say it has 50,90,40,90,80,60,80.

I know I need to remove the second, fifth and third elements. I don't necessarily always know the order of elements to remove, nor how many. The issue is by erasing an element, this changes the index of the other elements. Therefore, how could I erase these and compensate for the index change. (sorting then linearly erasing with an offset is not an option)

Thanks

8
"sorting then linearly erasing with an offset is not an option": Why? - Niki

8 Answers

35
votes

I am offering several methods:

1. A fast method that does not retain the original order of the elements:

Assign the current last element of the vector to the element to erase, then erase the last element. This will avoid big moves and all indexes except the last will remain constant. If you start erasing from the back, all precomputed indexes will be correct.

void quickDelete( int idx )
{
  vec[idx] = vec.back();
  vec.pop_back();
}

I see this essentially is a hand-coded version of the erase-remove idiom pointed out by Klaim ...

2. A slower method that retains the original order of the elements:

Step 1: Mark all vector elements to be deleted, i.e. with a special value. This has O(|indexes to delete|).

Step 2: Erase all marked elements using v.erase( remove (v.begin(), v.end(), special_value), v.end() );. This has O(|vector v|).

The total run time is thus O(|vector v|), assuming the index list is shorter than the vector.

3. Another slower method that retains the original order of the elements:

Use a predicate and remove if as described in https://stackoverflow.com/a/3487742/280314 . To make this efficient and respecting the requirement of not "sorting then linearly erasing with an offset", my idea is to implement the predicate using a hash table and adjust the indexes stored in the hash table as the deletion proceeds on returning true, as Klaim suggested.

13
votes

Using a predicate and the algorithm remove_if you can achieve what you want : see http://www.cplusplus.com/reference/algorithm/remove_if/

Don't forget to erase the item (see remove-erase idiom).

Your predicate will simply hold the idx of each value to remove and decrease all indexes it keeps each time it returns true.

That said if you can afford just removing each object using the remove-erase idiom, just make your life simple by doing it.

9
votes

Erase the items backwards. In other words erase the highest index first, then next highest etc. You won't invalidate any previous iterators or indexes so you can just use the obvious approach of multiple erase calls.

6
votes

I would move the elements which you don't want to erase to a temporary vector and then replace the original vector with this.

3
votes

While this answer by Peter G. in variant one (the swap-and-pop technique) is the fastest when you do not need to preserve the order, here is the unmentioned alternative which maintains the order.

With C++17 and C++20 the removal of multiple elements from a vector is possible with standard algorithms. The run time is O(N * Log(N)) due to std::stable_partition. There are no external helper arrays, no excessive copying, everything is done inplace. Code is a "one-liner":

template <class T>
inline void erase_selected(std::vector<T>& v, const std::vector<int>& selection)
{
    v.resize(std::distance(
        v.begin(),
        std::stable_partition(v.begin(), v.end(),
             [&selection, &v](const T& item) {
                  return !std::binary_search(
                      selection.begin(),
                      selection.end(),
                      static_cast<int>(static_cast<const T*>(&item) - &v[0]));
        })));
}

The code above assumes that selection vector is sorted (if it is not the case, std::sort over it does the job, obviously).

To break this down, let us declare a number of temporaries:

// We need an explicit item index of an element
// to see if it should be in the output or not
int itemIndex = 0;
// The checker lambda returns `true` if the element is in `selection`
auto filter = [&itemIndex, &sorted_sel](const T& item) {
    return !std::binary_search(
                      selection.begin(),
                      selection.end(),
                      itemIndex++);
};

This checker lambda is then fed to std::stable_partition algorithm which is guaranteed to call this lambda only once for each element in the original (unpermuted !) array v.

auto end_of_selected = std::stable_partition(
                           v.begin(),
                           v.end(),
                           filter);

The end_of_selected iterator points right after the last element which should remain in the output array, so we now can resize v down. To calculate the number of elements we use the std::distance to get size_t from two iterators.

v.resize(std::distance(v.begin(), end_of_selected));

This is different from the code at the top (it uses itemIndex to keep track of the array element). To get rid of the itemIndex, we capture the reference to source array v and use pointer arithmetic to calculate itemIndex internally.

Over the years (on this and other similar sites) multiple solutions have been proposed, but usually they employ multiple "raw loops" with conditions and some erase/insert/push_back calls. The idea behind stable_partition is explained beautifully in this talk by Sean Parent.

This link provides a similar solution (and it does not assume that selection is sorted - std::find_if instead of std::binary_search is used), but it also employs a helper (incremented) variable which disables the possibility to parallelize processing on larger arrays.

Starting from C++17, there is a new first argument to std::stable_partition (the ExecutionPolicy) which allows auto-parallelization of the algorithm, further reducing the run-time for big arrays. To make yourself believe this parallelization actually works, there is another talk by Hartmut Kaiser explaining the internals.

1
votes

Would this work:

void DeleteAll(vector<int>& data, const vector<int>& deleteIndices)
{
    vector<bool> markedElements(data.size(), false);
    vector<int> tempBuffer;
    tempBuffer.reserve(data.size()-deleteIndices.size());

    for (vector<int>::const_iterator itDel = deleteIndices.begin(); itDel != deleteIndices.end(); itDel++)
        markedElements[*itDel] = true;

    for (size_t i=0; i<data.size(); i++)
    {
        if (!markedElements[i])
            tempBuffer.push_back(data[i]);
    }
    data = tempBuffer;
}

It's an O(n) operation, no matter how many elements you delete. You could gain some efficiency by reordering the vector inline (but I think this way it's more readable).

0
votes

You can use this method, if the order of the remaining elements doesn't matter

#include <iostream> 
#include <vector>

using namespace std;
int main()
{
    vector< int> vec;
    vec.push_back(1);
    vec.push_back(-6);
    vec.push_back(3);
    vec.push_back(4);
    vec.push_back(7);
    vec.push_back(9);
    vec.push_back(14);
    vec.push_back(25);
    cout << "The elements befor " << endl;
    for(int i = 0; i < vec.size(); i++) cout << vec[i] <<endl;
    vector< bool> toDeleted;
    int YesOrNo = 0;
    for(int i = 0; i<vec.size(); i++)
    {
        
        cout<<"You need to delete this element? "<<vec[i]<<", if yes enter 1 else enter 0"<<endl;
        cin>>YesOrNo;
        if(YesOrNo)
            toDeleted.push_back(true);
        else
            toDeleted.push_back(false);
    }
    //Deleting, beginning from the last element to the first one
    for(int i = toDeleted.size()-1; i>=0; i--)
    {
        if(toDeleted[i])
        {
            vec[i] = vec.back();
            vec.pop_back();
        }
    }
    cout << "The elements after" << endl;
    for(int i = 0; i < vec.size(); i++) cout << vec[i] <<endl;
    return 0;
}
0
votes

This is non-trival because as you delete elements from the vector, the indexes change.

[0] hi
[1] you
[2] foo

>> delete [1]
[0] hi
[1] foo

If you keep a counter of times you delete an element and if you have a list of indexes you want to delete in sorted order then:

int counter = 0;
for (int k : IndexesToDelete) {
  events.erase(events.begin()+ k + counter);
  counter -= 1;
}