Search code examples
c++c++11vectormove-semanticserase-remove-idiom

remove arbitrary list of items from std::vector<std::vector<T> >


I have a vector of vectors, representing an array. I would like to remove rows efficiently, ie with minimal complexity and allocations

I have thought about building a new vector of vectors, copying only non-deleted rows, using move semantics, like this:

    //std::vector<std::vector<T> > values is the array to remove rows from
    //std::vector<bool> toBeDeleted contains "marked for deletion" flags for each row

    //Count the new number of remaining rows
    unsigned int newNumRows = 0;
    for(unsigned int i=0;i<numRows();i++)
    {
        if(!toBeDeleted[i])
        {
            newNumRows++;
        }
    }


    //Create a new array already sized in rows
    std::vector<std::vector<T> > newValues(newNumRows);

    //Move rows
    for(unsigned int i=0;i<numRows();i++)
    {
        if(!toBeDeleted[i])
        {
            newValues[i] = std::move(values[i]);
        }
    }

    //Set the new array and clear the old one efficiently
    values = std::move(newValues);

Is this the most effective way?

Edit : I just figured that I could avoid allocating a new array by moving rows down iteratively, this could be slightly more efficient and code is much more simple:

    unsigned int newIndex = 0;
    for(unsigned int oldIndex=0;oldIndex<values.size();oldIndex++)
    {
        if(!toBeDeleted[oldIndex])
        {
            if(oldIndex!=newIndex)
            {
                values[newIndex] = std::move(values[oldIndex]);
            }

            newIndex++;
        }
    }
    values.resize(newIndex);

Thanks!


Solution

  • This can be solved using a variation on the usual erase-remove idiom, with a lambda inside the std::remove_if that looks up the index of the current row inside an iterator range of to be removed indices:

    #include <algorithm>    // find, remove_if
    #include <iostream>
    #include <vector>
    
    template<class T>
    using M = std::vector<std::vector<T>>; // matrix
    
    template<class T>
    std::ostream& operator<<(std::ostream& os, M<T> const& m)
    {
        for (auto const& row : m) {
            for (auto const& elem : row)
                os << elem << " ";
            os << "\n";
        }
        return os;
    }
    
    template<class T, class IdxIt>
    void erase_rows(M<T>& m, IdxIt first, IdxIt last)
    {
        m.erase(
            std::remove_if(
                begin(m), end(m), [&](auto& row) {
                auto const row_idx = &row - &m[0];
                return std::find(first, last, row_idx) != last;
            }), 
            end(m)
        );
    }
    
    int main()
    {
        auto m = M<int> { { 0, 1, 2, 3 }, { 3, 4, 5, 6 }, { 6, 7, 8, 9 }, { 1, 0, 1, 0 } };
        std::cout << m << "\n";
    
        auto drop = { 1, 3 };
        erase_rows(m, begin(drop), end(drop));
    
        std::cout << m << "\n";
    }
    

    Live Example.

    Note: because from C++11 onwards, std::vector has move semantics, shuffling rows around in your std::vector<std::vector<T>> is done using simple pointer manipulations, regardless of your type T (it would be quite different if you want column-deletion, though!).