关于c ++:使用索引擦除stl :: vector中的元素

Erasing elements in stl::vector by using indexes

我有一个stl::vector,我需要删除给定索引处的所有元素(向量通常具有高维数)。我想知道,考虑到原始向量的顺序应该保持不变,哪种方法最有效。

虽然,我在这个问题上找到了相关的文章,但其中一些文章需要删除一个元素或多个元素,而删除-删除习惯用法似乎是一个很好的解决方案。然而,在我的例子中,我需要删除多个元素,因为我使用的是索引而不是直接值,所以不能应用remove-erase idiom,对吗?下面给出了我的代码,我想知道在效率方面是否可以做得更好?

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()方法不再是一个选项


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