使用索引擦除stl :: vector中的元素

时间:2011-07-07 11:00:04

标签: c++ algorithm stl vector

我有一个stl::vector<int>,我需要删除给定索引处的所有元素(向量通常具有高维度)。我想知道,考虑到原始载体的顺序应该保留,这是进行这种操作的最有效方法。

虽然我在这个问题上发现了相关帖子,但其中一些帖子需要删除一个single elementmultiple elements,其中remove-erase idiom似乎是一个很好的解决方案。 但是,在我的情况下,我需要删除多个元素,因为我使用的是索引而不是直接值,所以remove-erase idiom无法应用,对吧? 我的代码如下所示,我想知道在效率方面是否可以做得更好?

bool find_element(const vector<int> & vMyVect, int nElem){
    return (std::find(vMyVect.begin(), vMyVect.end(), nElem)!=vMyVect.end()) ? true : false;
}

void remove_elements(){

    srand ( time(NULL) );

    int nSize = 20;
    std::vector<int> vMyValues;
    for(int i = 0; i < nSize; ++i){
            vMyValues.push_back(i);
    }

    int nRandIdx;
    std::vector<int> vMyIndexes;
    for(int i = 0; i < 6; ++i){
        nRandIdx = rand() % nSize;
        vMyIndexes.push_back(nRandIdx);
    }

    std::vector<int> vMyResult;
    for(int i=0; i < (int)vMyValues.size(); i++){
        if(!find_element(vMyIndexes,i)){
            vMyResult.push_back(vMyValues[i]);
        }
    }
}

6 个答案:

答案 0 :(得分:22)

我认为它可能更有效率,如果您只是对索引进行排序,然后从矢量中删除从最高到最低的元素。删除列表中的最高索引不会使您要删除的较低索引无效,因为只有高于已删除元素的元素才会更改其索引。

如果真的更有效则取决于排序的速度。关于这个解决方案的另一个专家是,你不需要你的值向量的副本,你可以直接在原始向量上工作。代码看起来像这样:

... fill up the vectors ...

sort (vMyIndexes.begin(), vMyIndexes.end());

for(int i=vMyIndexes.size() - 1; i >= 0; i--){
    vMyValues.erase(vMyValues.begin() + vMyIndexes[i])
}

答案 1 :(得分:6)

为避免多次移动相同的元素,我们可以按删除的索引之间的范围移动它们

// fill vMyIndexes, take care about duplicated values
vMyIndexes.push_back(-1); // to handle range from 0 to the first index to remove
vMyIndexes.push_back(vMyValues.size()); // to handle range from the last index to remove and to the end of values
std::sort(vMyIndexes.begin(), vMyIndexes.end());
std::vector<int>::iterator last = vMyValues.begin();
for (size_t i = 1; i != vMyIndexes.size(); ++i) {
    size_t range_begin = vMyIndexes[i - 1] + 1;
    size_t range_end = vMyIndexes[i];
    std::copy(vMyValues.begin() + range_begin, vMyValues.begin() + range_end,   last);
    last += range_end - range_begin;
}
vMyValues.erase(last, vMyValues.end());

P.S。修复了一个错误,感谢Steve Jessop耐心地试图向我展示

答案 2 :(得分:3)

擦除 - 删除给定索引

的多个元素

更新:在@kory对性能的反馈之后,我修改了算法,不使用标记和移动/复制元素(不是逐个)。< / p> 备注:

  • 指数需要排序且唯一
  • 使用std::move(将std::copy替换为c ++ 98):

Github Live example

代码:
template <class ForwardIt, class SortUniqIndsFwdIt>
inline ForwardIt remove_at(
    ForwardIt first,
    ForwardIt last,
    SortUniqIndsFwdIt ii_first,
    SortUniqIndsFwdIt ii_last)
{
    if(ii_first == ii_last) // no indices-to-remove are given
        return last;
    typedef typename std::iterator_traits<ForwardIt>::difference_type diff_t;
    typedef typename std::iterator_traits<SortUniqIndsFwdIt>::value_type ind_t;
    ForwardIt destination = first + static_cast<diff_t>(*ii_first);
    while(ii_first != ii_last)
    {
        // advance to an index after a chunk of elements-to-keep
        for(ind_t cur = *ii_first++; ii_first != ii_last; ++ii_first)
        {
            const ind_t nxt = *ii_first;
            if(nxt - cur > 1)
                break;
            cur = nxt;
        }
        // move the chunk of elements-to-keep to new destination
        const ForwardIt source_first =
            first + static_cast<diff_t>(*(ii_first - 1)) + 1;
        const ForwardIt source_last =
            ii_first != ii_last ? first + static_cast<diff_t>(*ii_first) : last;
        std::move(source_first, source_last, destination);
        // std::copy(source_first, source_last, destination) // c++98 version
        destination += source_last - source_first;
    }
    return destination;
}
用法示例:
std::vector<int> v = /*...*/; // vector to remove elements from
std::vector<int> ii = /*...*/; // indices of elements to be removed

// prepare indices
std::sort(ii.begin(), ii.end());
ii.erase(std::unique(ii.begin(), ii.end()), ii.end());

// remove elements at indices
v.erase(remove_at(v.begin(), v.end(), ii.begin(), ii.end()), v.end());

答案 3 :(得分:2)

您可以做的是将矢量(实际上是任何非关联容器)分成两部分 组,一个对应于要删除的索引,另一个包含其余的。

template<typename Cont, typename It>
auto ToggleIndices(Cont &cont, It beg, It end) -> decltype(std::end(cont))
{
    int helpIndx(0);
    return std::stable_partition(std::begin(cont), std::end(cont), 
        [&](typename Cont::value_type const& val) -> bool {
            return std::find(beg, end, helpIndx++) != end;
    });
}

然后您可以从分割点删除(或最多)以删除(仅保留) 对应于指数的元素

std::vector<int> v;
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);

int ar[] = { 2, 0, 4 };
v.erase(ToggleIndices(v, std::begin(ar), std::end(ar)), v.end());
  • 如果不需要'仅通过索引保持'操作,您可以使用remove_if insted of stable_partition(O(n)vs O(nlogn)复杂度)
  • 要将C数组用作容器,lambda函数应该是 [&amp;](decltype(*(std :: begin(cont)))const&amp; val) - &gt;布尔 {return std :: find(beg,end,helpIndx ++)!= end; } 但是.erase()方法不再是一个选项

答案 4 :(得分:1)

如果要确保每个元素只移动一次,您可以简单地遍历每个元素,将要保留的元素复制到新的第二个容器中,不要复制要删除的元素,然后删除旧容器并用新容器替换它:)

答案 5 :(得分:0)

这是基于Andriy Tylychkoanswer的算法,因此可以更轻松,更快速地使用答案,而不必将其分开。它还消除了在索引列表的开始处具有-1和在结尾处具有多个items的需要。还有一些调试代码可确保indices是有效的(已归入items的有效索引中)。

template <typename Items_it, typename Indices_it>
auto remove_indices(
    Items_it items_begin, Items_it items_end
  , Indices_it indices_begin, Indices_it indices_end
)
{
    static_assert(
      std::is_same_v<std::random_access_iterator_tag
        , typename std::iterator_traits<Items_it>::iterator_category>
      , "Can't remove items this way unless Items_it is a random access iterator");

    size_t indices_size = std::distance(indices_begin, indices_end);
    size_t items_size = std::distance(items_begin, items_end);
    if (indices_size == 0) {
        // Nothing to erase
        return items_end;
    }

    // Debug check to see if the indices are already sorted and are less than
    // size of items.
    assert(indices_begin[0] < items_size);
    assert(std::is_sorted(indices_begin, indices_end));

    auto last = items_begin;
    auto shift = [&last, &items_begin](size_t range_begin, size_t range_end) {
        std::copy(items_begin + range_begin, items_begin + range_end, last);
        last += range_end - range_begin;
    };

    size_t last_index = -1;
    for (size_t i = 0; i != indices_size; ++i) {
        shift(last_index + 1, indices_begin[i]);
        last_index = indices_begin[i];
    }
    shift(last_index + 1, items_size);
    return last;
}

以下是用法示例:

template <typename T>
std::ostream& operator<<(std::ostream& os, std::vector<T>& v)
{
    for (auto i : v) {
        os << i << " ";
    }
    os << std::endl;
    return os;
}

int main()
{
    using std::begin;
    using std::end;
    std::vector<int> items = { 1, 3, 6, 8, 13, 17 };
    std::vector<int> indices = { 0, 1, 2, 3, 4 };

    std::cout << items;
    items.erase(
          remove_indices(begin(items), end(items), begin(indices), end(indices))
        , std::end(items)
    );
    std::cout << items;

    return 0;
}

输出:

1 3 6 8 13 17 
17 

所需的标题为:

#include <iterator>
#include <vector>
#include <iostream> // only needed for output
#include <cassert>
#include <type_traits>

在godbolt.org上可以找到 Demo