Erasing elements in stl::vector by using indexes
我有一个
虽然,我在这个问题上找到了相关的文章,但其中一些文章需要删除一个元素或多个元素,而删除-删除习惯用法似乎是一个很好的解决方案。然而,在我的例子中,我需要删除多个元素,因为我使用的是索引而不是直接值,所以不能应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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]); } } } |
我认为,如果只对索引排序,然后从向量中从最高到最低删除这些元素,这可能更有效。删除列表中的最高索引不会使要删除的较低索引无效,因为只有高于已删除索引的元素才会更改其索引。
如果真的更有效,将取决于排序的速度。关于这个解决方案的另一个优点是,您不需要复制值向量,可以直接在原始向量上工作。代码应该如下所示:
1 2 3 4 5 6 7 | ... 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 2 3 4 5 6 7 8 9 10 11 12 | // 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.修复了一个bug,感谢史蒂夫·杰索普耐心地给我看。
删除删除给定索引处的多个元素
更新:在@kory对性能的反馈之后,我修改了算法,不使用标记和分块移动/复制元素(不是一个接一个)。
笔记:- 索引需要排序并唯一
- 使用EDCOX1×0×(用EDCOX1对C++ 98替换1):
GITHUB(GITHUB)LIve示例
代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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; } |
使用实例:
1 2 3 4 5 6 7 8 9 | 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()); |
您可以将向量(实际上是任何非关联容器)拆分为两个组,其中一个对应于要删除的索引,另一个包含其余的索引。
1 2 3 4 5 6 7 8 9 | 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; }); } |
然后可以从要删除的分割点(或最多)删除(仅保留)与索引对应的元素
1 2 3 4 5 6 7 8 9 10 | 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_(如果安装了稳定的_分区(o(n)vs o(nlogn)复杂性)
- 要将C数组用作容器,lambda函数应该[&;](decltype(((std::begin(cont)))const&val)->bool返回std::find(beg,end,helpindx++)!=结束;}但是.erase()方法不再是一个选项
如果要确保每个元素只移动一次,可以简单地遍历每个元素,将要保留的元素复制到新的第二个容器中,不要复制要删除的元素,然后删除旧容器并将其替换为新容器:)