6

Say I have a std::vector with 5 elements and I need to delete elements from indexes 1 and 3 what would be the fastest way to do this. Are there any helper methods in the standard library that could do this for me ?

MistyD
  • 16,373
  • 40
  • 138
  • 240

4 Answers4

11

You could use the erase function. For this specific case you mention something like this:

myvector.erase (myvector.begin()+3);
myvector.erase (myvector.begin()+1);

will do the trick. You have to provide an iterator to the erase function and I suggest you read documentation for its use. The above should work for your case. Note that each call to erase will change the index of the remaining elements AFTER the removed position though as the internal array elements will be adjusted relative to the item removed.

In response to your comment, you can only erase one element at a time, UNLESS they are contiguous indices in which case you can use the range based version of erase taking a start and end iterator. For example if you want to erase indices 1,2 AND 3 use

myvector.erase (myvector.begin()+1,myvector.begin()+4);

As I already mentioned the indices of items after the one you erase will downshift accordingly. This is unavoidable though as an array cannot have "gaps" in it.

mathematician1975
  • 21,161
  • 6
  • 59
  • 101
  • 1
    would'nt `myvector.erase (myvector.begin()+3);` change the index of the other elements. What if I had 6 elements and wanted to remove 1 ,3 and 5 ? – MistyD Dec 14 '13 at 11:18
  • 6
    @MistyD Then you erase 5 then 3 then 1. Only indices AFTER the one erased will be affected, so go in reverse index order. –  Dec 14 '13 at 11:19
7

This should be a fairly efficient implementation using std::move and only moving each element max once. It requires the indexes to remove in to_remove to be ordered.

template<typename T>
  void remove_index(std::vector<T>& vector, const std::vector<int>& to_remove)
  {
    auto vector_base = vector.begin();
    std::vector<T>::size_type down_by = 0;

    for (auto iter = to_remove.cbegin(); 
              iter < to_remove.cend(); 
              iter++, down_by++)
    {
      std::vector<T>::size_type next = (iter + 1 == to_remove.cend() 
                                        ? vector.size() 
                                        : *(iter + 1));

      std::move(vector_base + *iter + 1, 
                vector_base + next, 
                vector_base + *iter - down_by);
    }
    vector.resize(vector.size() - to_remove.size());
  }

// Usage:
//
// std::vector<std::string> values = { "0", "1", "2", "3", "4", "5"};
// remove_index(values, { 1, 3 });
Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
2

This is a lot faster than removing them one-by-one (though it can still be sped up in some cases):

template<class It>
struct remover
{
    size_t *i;
    It *begin;
    It const *end;
    explicit remover(size_t &i, It &begin, It const &end) : i(&i), begin(&begin), end(&end) { }
    template<class T>
    bool operator()(T const &)
    {
        size_t &i = *this->i;
        It &begin = *this->begin;
        It const &end = *this->end;
        while (begin != end && *begin < i)  /* only necessary in case there are duplicate indices */
        { ++begin;  }
        bool const b = begin != end && *begin == i;
        if (b) { ++begin; }
        ++i;
        return b;
    }
};
template<class Container, class IndexIt>
IndexIt remove_indices(Container &items, IndexIt indices_begin, IndexIt const &indices_end)
{
    size_t i = 0;
    std::sort(indices_begin, indices_end);
    items.erase(std::remove_if(items.begin(), items.end(), remover<IndexIt>(i, indices_begin, indices_end)), items.end());
    return indices_begin;
}
int main()
{
    std::vector<int> items(100);
    std::vector<size_t> indices;
    indices.push_back(5);
    indices.push_back(1);
    remove_indices(items, indices.begin(), indices.end());
}
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    +1 I was gonna write the same thing. Some people won't like it because your functor for `remove_if` is conceptualy not a predicate (its return value doesn not depend on its input) and because `remove_if` isn't officialy guaranteed to iterate in order. – jrok Dec 14 '13 at 11:36
  • @jrok: Shoot, it's not guaranteed to iterate in order?? I didn't know that... – user541686 Dec 14 '13 at 11:37
  • Well, the required iterator category is `ForwardIterator` and if that's its actual category it's got no choice but to go in order. But algorithms can be internaly specialized for different iterator categories (we had this discusion before about `remove_if` and that's what I remember the conclusion was). – jrok Dec 14 '13 at 11:39
  • @jrok: Interesting. I guess even with a forward iterator there wouldn't be a guarantee either right? It might do indices 1, 0, 3, 2, 5, 4, 7, 6, ... for all I know... – user541686 Dec 14 '13 at 11:41
  • As others have pointed out, this does not return a correct result (some elements remaining) in my testing, but it is about 10 times faster than more correct results and 100 times faster than Vlad from Moscow's code. – kory Mar 29 '19 at 14:36
  • @kory: Sorry, could you give an example of a test case where some elements remain? – user541686 Mar 29 '19 at 18:24
  • Yes, if I have a way to send you some example code I could do that, but in my testing I have a vector with a pair of doubles std::vector> (or maybe they were ints) of size 130,547. I then create a random distributed int generator from 0 to the size of the array and try to remove 547 elements with the indices taken from this random generator. So the indices taken are randomly distributed throughout the vector. Anyhow, after running your example code I end up with a count of 130,160 or something like that. I could send you the code if you want to fix the issue. – kory Mar 30 '19 at 21:28
  • @kory: You kept the `std::sort` line, right? That's part of the code. If you did and you still see a problem then please paste the code on https://pastebin.com so I can fix it. – user541686 Mar 30 '19 at 22:07
  • I figured out what the issue is and it occurs when you have a single repeated value in the indices list. I end up trying to remove 547 objects from 130,547 but with one repeated value it only drops the number down to 130,169. I can post the code to you but at least now I understand why without having to dig too much into it. – kory Apr 01 '19 at 02:11
  • ```...include algorithm,functional,iostream,random,vector and add your remover... int main() { std::vector items(130547); std::vector indices; auto rand = std::bind(std::uniform_int_distribution(0, (int)items.size() - 1), std::mt19937(1116)); for (int i = 0; i < 547; ++i) { indices.push_back(rand()); } std::sort(indices.begin(), indices.end()); items.erase( std::remove_if( items.begin(), items.end(), make_remover(indices.begin(), indices.end())), items.end()); std::cout << "Item Count = " << items.size() << std::endl; return 0; }``` – kory Apr 01 '19 at 02:16
  • Here are the timings from some of the various solutions here: stable_partition (0.022s) Vlad from Moscow (0.021s) foreach erase on const iter (0.192s) Mehrdad (0.0003s) – kory Apr 01 '19 at 02:19
  • @kory: Ahh, yes, I hadn't thought about duplicate indices! It is a little unclear what it means to remove the same index twice but I think you're right, the current behavior probably isn't what we want. I'll fix it, thanks! – user541686 Apr 01 '19 at 02:21
  • I have put the code and VS solution files here, it includes my timing code as well, enjoy! https://1drv.ms/u/s!AqfHs32TQOK2gv0O2EwoFoQshHtbsQ – kory Apr 01 '19 at 02:28
  • @kory: Thanks! I updated the code, hopefully it works now. – user541686 Apr 01 '19 at 03:01
  • Yes, it works, sad thing of course is that it runs a bit slower but still lightyears ahead of the rest: Vlad from Moscow: (0.0186771s) Version 2: (0.0003792s) Version 1: (0.0002581s) – kory Apr 01 '19 at 03:21
  • @kory: Yeah, I mean, the rest are just using poor algorithms. You can actually be more clever with this one too and make it faster if your data has interesting patterns, but it takes quite a bit more code so I haven't tried to. – user541686 Apr 01 '19 at 03:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/191072/discussion-between-kory-and-mehrdad). – kory Apr 02 '19 at 09:08
0

If you want to erase some arbitrary elements in a vector you can do this the following way

for ( int i : { 3, 1 } ) v.erase( std::next( v.begin(), i ) );

Take into account that items in the initializer list should be in descending order.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335