anti
anti

Reputation: 3125

fastest way to remove items from multiple std::vectors

I have three std::vectors, each containing different data types.

What I need to do is remove the same index item from each, depending on the value of that indexed item in the first one.

In the code below, if the value of localMap_points[i].m_counter is more than 30, I erase the item at index [i] from all three vectors. (localMap_desc contains 8 items for the other vectors one, hence the x 8 added to that one)

This works perfectly, but is slow. Is there a faster way to do this?

I have:

for (int i = 0; i < localMap_points.size(); i++)
{
    if (localMap_points[i].m_counter > 30)
    {
        localMap_kp.erase(localMap_kp.begin() + i, localMap_kp.begin() + i + 1); // Deleting from n element to n element
        localMap_desc.erase(localMap_desc.begin() + (i * 8), localMap_desc.begin() + (i * 8) + 8); // Deleting from n element to n element X 8
        localMap_points.erase(localMap_points.begin() + i, localMap_points.begin() + i + 1); // Deleting from n element to n element

    }

}

Upvotes: 3

Views: 492

Answers (3)

rustyx
rustyx

Reputation: 85351

Here's an example how you can apply the erase-remove idiom for three vectors at once.

The algorithm in O(N), i.e. requires a single pass over the vectors.

template<typename T1, typename T2, typename T3, typename P>
void erase3(T1& v1, T2& v2, T3& v3, P predicate) {
    auto first1 = begin(v1);
    auto first2 = begin(v2);
    auto first3 = begin(v3);
    while (first1 != end(v1) && !predicate(*first1)) {
        ++first1; ++first2; ++first3;
    }
    if (first1 != end(v1)) {
        auto it1 = first1; auto it2 = first2; auto it3 = first3;
        while (++it1 != end(v1)) {
            ++it2;
            ++it3;
            if (!predicate(*it1)) {
                *first1++ = std::move(*it1);
                *first2++ = std::move(*it2);
                *first3++ = std::move(*it3);
            }
        }
        v1.erase(first1, end(v1));
        v2.erase(first2, end(v2));
        v3.erase(first3, end(v3));
    }
}

int main()
{
    std::vector<int> v1 = { 1,2,3,4,5 }, v2 = { 11,12,13,14,15 }, v3 = { 21,22,23,24,25 };

    erase3(v1, v2, v3, [](int a) { return a == 3 || a == 4; });

}

This works by moving all remaining elements to the beginning of the vectors and then trimming off the moved-out ones from the end.

Upvotes: 2

HappyCactus
HappyCactus

Reputation: 2014

Use a more proper data structure. vectors are not meant for fast internal deletion and insertion (the are O(n)), lists may be a better choice if you don't need indexed access (insert/delete is O(1) == constant time). Depending by your needs, you may use an helper structure, but it's not possible without further information.

--- Adding another thought (someone already suggested this though).

If order of vector doesn't matter, you could swap the item to be deleted with the last in the vector, and then pop_back().

It would be O(1).

--- Thinking out loud.

Another trick would be to use a priority queue. They are data structure that reorders itself based on the "weight" of one field, in your application it would be the counter field. they are able to insert in O(log n) and delete from top in O(1). So it would be quite fast to remove the items with higher counter, because they are always on top.

Upvotes: 2

lubgr
lubgr

Reputation: 38277

The performance bottleneck here is the memory layout of std::vector, possibly together with the special member function properties/existence of the vector elements. If you erase one element in the middle, all the elements from this position until the end must be moved to adjust to the element before the removed one. This is done via

  • one copy construction per element, if there is no move ctor or if the move ctor is not marked noexcept
  • one move construction per element otherwise.

The first thing to ensure is hence that the element type stored in the vector has a noexcept move constructor.

Second, make sure to use the erase-remove idiom here. By following this pattern, the reordering in terms of swap invocations is done first, before any calls to erase. For n items to be erased, those are n swap invocations. Then, you will have a trivial call to std::vector::erase, as all elements are already in place as they should. For this procedure to as fast as possible, you might want to consider providing a swap function for your custom types.

Upvotes: 4

Related Questions