简体   繁体   English

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

[英]Erasing elements in std::vector by using indexes

I've a std::vector<int> and I need to remove all elements at given indexes (the vector usually has high dimensionality).我有一个std::vector<int> ,我需要删除给定索引处的所有元素(向量通常具有高维)。 I would like to know, which is the most efficient way to do such an operation having in mind that the order of the original vector should be preserved.我想知道,考虑到应该保留原始向量的顺序,这是执行此类操作的最有效方法。

Although, I found related posts on this issue, some of them needed to remove one single element or multiple elements where the remove-erase idiom seemed to be a good solution.虽然,我找到了关于这个问题的相关帖子,但其中一些需要删除一个元素多个元素,而remove-erase 习惯用法似乎是一个很好的解决方案。 In my case, however, I need to delete multiple elements and since I'm using indexes instead of direct values, the remove-erase idiom can't be applied, right?但是,就我而言,我需要删除多个元素,并且由于我使用的是索引而不是直接值,因此不能应用remove-erase idiom ,对吧? My code is given below and I would like to know if it's possible to do better than that in terms of efficiency?我的代码在下面给出,我想知道在效率方面是否可以做得更好?

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]);
        }
    }
}

I think it could be more efficient, if you just just sort your indices and then delete those elements from your vector from the highest to the lowest.我认为它可能会更有效,如果您只是对索引进行排序,然后从向量中从最高到最低删除这些元素。 Deleting the highest index on a list will not invalidate the lower indices you want to delete, because only the elements higher than the deleted ones change their index.删除列表中的最高索引不会使您要删除的较低索引无效,因为只有高于已删除索引的元素才会更改其索引。

If it is really more efficient will depend on how fast the sorting is.如果它真的更有效将取决于排序的速度。 One more pro about this solultion is, that you don't need a copy of your value vector, you can work directly on the original vector.关于此解决方案的另一个优点是,您不需要值向量的副本,您可以直接在原始向量上工作。 code should look something like this:代码应如下所示:

... fill up the vectors ...

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

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

Erase-remove multiple elements at given indices擦除 - 删除给定索引处的多个元素

Update: after the feedback on performance from @kory, I've modified the algorithm not to use flagging and move/copy elements in chunks (not one-by-one).更新:在@kory 对性能的反馈之后,我修改了算法,不使用标记和移动/复制块中的元素(不是一个接一个)。

Notes: 笔记:
  • indices need to be sorted and unique索引需要排序且唯一
  • uses std::move (replace with std::copy for c++98):使用std::move (替换为 c++98 的std::copy ):

Github Live example Github实例

Code: 代码:
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());
Usage example: 使用示例:
 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());

to avoid moving the same elements many times, we can move them by ranges between deleted indexes为了避免多次移动相同的元素,我们可以通过删除索引之间的范围移动它们

// 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());

PS fixed a bug, thanks to Steve Jessop that patiently tried to show me it PS 修复了一个错误,感谢Steve Jessop耐心地向我展示

What you can do is split the vector (actually any non-associative container) in two groups, one corresponding to the indices to be erased and one containing the rest.您可以做的是将向量(实际上是任何非关联容器)分成两组,一组对应于要擦除的索引,另一组包含 rest。

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;
    });
}

you can then delete from (or up to) the split point to erase (keep only) the elements corresponding to the indices然后,您可以从(或最多)分割点删除以擦除(仅保留)与索引对应的元素

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());
  • If the 'keep only by index' operation is not needed you can use remove_if insted of stable_partition (O(n) vs O(nlogn) complexity)如果不需要“仅按索引保留”操作,您可以使用 remove_if insted of stable_partition (O(n) vs O(nlogn) 复杂度)
  • To work for C arrays as containers the lambda function should be [&](decltype(*(std::begin(cont))) const& val) -> bool { return std::find(beg, end, helpIndx++);= end. To work for C arrays as containers the lambda function should be [&](decltype(*(std::begin(cont))) const& val) -> bool { return std::find(beg, end, helpIndx++);=结尾。 } but then the .erase() method is no longer an option但随后 .erase() 方法不再是一个选项

If you want to ensure that every element is only moved once, you can simply iterate through each element, copy those that are to remain into a new, second container, do not copy the ones you wish to remove, and then delete the old container and replace it with the new one:)如果要确保每个元素只移动一次,则可以简单地遍历每个元素,将要保留的元素复制到新的第二个容器中,不要复制要删除的元素,然后删除旧容器并用新的替换它:)

This is an algorithm based on Andriy Tylychko 's answer so that this can make it easier and faster to use the answer, without having to pick it apart.这是一种基于Andriy Tylychko答案的算法,这样可以更轻松、更快速地使用答案,而无需将其分开。 It also removes the need to have -1 at the beginning of the indices list and a number of items at the end.它还消除了在索引列表开头添加 -1 并在末尾添加一些items的需要。 Also some debugging code to make sure the indices are valid (sorted and valid index into 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;
}

Here is an example of usage:这是一个使用示例:

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;
}

Output: Output:

1 3 6 8 13 17 
17 

The headers required are:所需的标题是:

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

And a Demo can be found on godbolt.org.可以在 godbolt.org 上找到Demo

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM