如何在给定索引列表的情况下从std :: vector中删除项目

时间:2011-09-27 15:45:45

标签: c++ algorithm

我有一个项目items的向量,以及应该从items删除的索引向量:

std::vector<T> items;
std::vector<size_t> indicesToDelete;

items.push_back(a);
items.push_back(b);
items.push_back(c);
items.push_back(d);
items.push_back(e);

indicesToDelete.push_back(3);
indicesToDelete.push_back(0);
indicesToDelete.push_back(1);

// given these 2 data structures, I want to remove items so it contains
// only c and e (deleting indices 3, 0, and 1)
// ???

执行删除的最佳方法是什么,知道每次删除都会影响indicesToDelete中的所有其他索引?

一些想法是:

  1. 一次将items复制到新的向量中,如果索引位于indicesToDelete
  2. ,则跳过
  3. 迭代items,对于每次删除,减少indicesToDelete中索引较大的所有项目。
  4. 首先排序indicesToDelete,然后重复indicesToDelete,并为每个删除增量增加indexCorrection,从后续索引中减去。{/ li>

    所有人似乎都在思考这样一个看似微不足道的任务。有更好的想法吗?


    编辑这是解决方案,基本上是#1的变体,但使用迭代器来定义要复制到结果的块。

    template<typename T>
    inline std::vector<T> erase_indices(const std::vector<T>& data, std::vector<size_t>& indicesToDelete/* can't assume copy elision, don't pass-by-value */)
    {
        if(indicesToDelete.empty())
            return data;
    
        std::vector<T> ret;
        ret.reserve(data.size() - indicesToDelete.size());
    
        std::sort(indicesToDelete.begin(), indicesToDelete.end());
    
        // new we can assume there is at least 1 element to delete. copy blocks at a time.
        std::vector<T>::const_iterator itBlockBegin = data.begin();
        for(std::vector<size_t>::const_iterator it = indicesToDelete.begin(); it != indicesToDelete.end(); ++ it)
        {
            std::vector<T>::const_iterator itBlockEnd = data.begin() + *it;
            if(itBlockBegin != itBlockEnd)
            {
                std::copy(itBlockBegin, itBlockEnd, std::back_inserter(ret));
            }
            itBlockBegin = itBlockEnd + 1;
        }
    
        // copy last block.
        if(itBlockBegin != data.end())
        {
            std::copy(itBlockBegin, data.end(), std::back_inserter(ret));
        }
    
        return ret;
    }
    

7 个答案:

答案 0 :(得分:12)

我会选择1/3,即:命令索引向量,在数据向量中创建两个迭代器,一个用于读取,一个用于写入。将写入迭代器初始化为要删除的第一个元素,并将读取迭代器初始化为超出该元素的迭代器。然后在循环的每个步骤中将迭代器增加到下一个值(写入),并且不要跳过下一个值(读取)并复制/移动元素。在循环结束时调用erase以丢弃最后写入位置之外的元素。

BTW,这是在STL的 remove / remove_if 算法中实现的方法,区别在于您将条件保存在单独的有序向量中。

答案 1 :(得分:4)

std::sort() indicesToDelete按降序排列,然后从正常item循环中的for中删除。无需调整指数。

答案 2 :(得分:2)

甚至可能是选项4:

如果您要从大数字中删除一些项目,并且知道永远不会有高密度的已删除项目:

将应删除的索引中的每个项目替换为“tombstone”值,表示这些索引中没有任何内容,并确保无论何时访问项目,都要检查墓碑。

答案 3 :(得分:1)

这取决于您要删除的数字。

如果要删除许多项目,将未删除的项目复制到新矢量然后用新矢量替换旧矢量(在排序indicesToDelete之后)可能是有意义的。这样,您将避免在每次删除后压缩向量,这是一个O(n)操作,可能使整个过程为O(n ^ 2)。

如果要删除一些项目,也许可以按反向索引顺序删除(假设索引已排序),那么您不需要在项目被删除时进行调整。

答案 4 :(得分:1)

由于讨论已经转化为与性能相关的问题,我已经编写了以下代码。它使用remove_ifvector::erase,它应该将元素移动最少次数。有一些开销,但对于大型案例,这应该是好的。

但是,如果您不关心元素的相对顺序,那么这将不会那么快。

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <set>

using std::vector;
using std::string;
using std::remove_if;
using std::cout;
using std::endl;
using std::set;

struct predicate {
    public:
        predicate(const vector<string>::iterator & begin, const vector<size_t> & indices) {
            m_begin = begin;
            m_indices.insert(indices.begin(), indices.end());
        }

        bool operator()(string & value) {
            const int index = distance(&m_begin[0], &value);
            set<size_t>::iterator target = m_indices.find(index);
            return target != m_indices.end();
        }

    private:
        vector<string>::iterator m_begin;
        set<size_t> m_indices;
};

int main() {
    vector<string> items;
    items.push_back("zeroth");
    items.push_back("first");
    items.push_back("second");
    items.push_back("third");
    items.push_back("fourth");
    items.push_back("fifth");

    vector<size_t> indicesToDelete;
    indicesToDelete.push_back(3);
    indicesToDelete.push_back(0);
    indicesToDelete.push_back(1);

    vector<string>::iterator pos = remove_if(items.begin(), items.end(), predicate(items.begin(), indicesToDelete));
    items.erase(pos, items.end());

    for (int i=0; i< items.size(); ++i)
        cout << items[i] << endl;
}

这个的输出是:

second
fourth
fifth

还有一点性能开销仍然可以降低。在remove_if(至少在gcc上)中,谓词是按向量中每个元素的值复制的。这意味着我们每次都可能在集合m_indices上执行复制构造函数。如果编译器无法摆脱这种情况,那么我建议将索引作为一个集合传递,并将其存储为const引用。

我们可以这样做:

struct predicate {
    public:
        predicate(const vector<string>::iterator & begin, const set<size_t> & indices) : m_begin(begin), m_indices(indices) {
        }

        bool operator()(string & value) {
            const int index = distance(&m_begin[0], &value);
            set<size_t>::iterator target = m_indices.find(index);
            return target != m_indices.end();
        }

    private:
        const vector<string>::iterator & m_begin;
        const set<size_t> & m_indices;
};

int main() {
    vector<string> items;
    items.push_back("zeroth");
    items.push_back("first");
    items.push_back("second");
    items.push_back("third");
    items.push_back("fourth");
    items.push_back("fifth");

    set<size_t> indicesToDelete;
    indicesToDelete.insert(3);
    indicesToDelete.insert(0);
    indicesToDelete.insert(1);

    vector<string>::iterator pos = remove_if(items.begin(), items.end(), predicate(items.begin(), indicesToDelete));
    items.erase(pos, items.end());

    for (int i=0; i< items.size(); ++i)
        cout << items[i] << endl;
}

答案 5 :(得分:0)

基本上问题的关键是要记住,如果删除索引i处的对象,并且不使用墓碑占位符,则向量必须复制所有 i之后的对象。除了#1之外,这适用于您建议的所有可能性。复制到新列表会生成一个副本,无论您删除多少,都可以通过获得最快的答案。
正如DavidRodríguez所说,对要删除的索引列表进行排序可以进行一些小的优化,但是如果你删除的数量超过10-20,那么它可能是值得的(请先说明一下)。

答案 6 :(得分:0)

以下是此问题的解决方案,它保持原始“项目”的顺序:

  1. 创建一个“矢量蒙版”并用“假”值初始化(填充)它。
  2. 将要删除的所有索引的掩码值更改为“true”。
  3. 遍历“mask”的所有成员并从两个向量“items”中删除并“掩盖”具有“true”值的元素。
  4. 以下是代码示例:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int main()
    {
        vector<unsigned int> items(12);
        vector<unsigned int> indicesToDelete(3);
        indicesToDelete[0] = 3;
        indicesToDelete[1] = 0;
        indicesToDelete[2] = 1;
        for(int i=0; i<12; i++) items[i] = i;
    
        for(int i=0; i<items.size(); i++)
          cout << "items[" << i << "] = " << items[i] << endl;
    
        // removing indeces
        vector<bool> mask(items.size());
        vector<bool>::iterator mask_it;
        vector<unsigned int>::iterator items_it;
        for(size_t i = 0; i < mask.size(); i++)
          mask[i] = false;
        for(size_t i = 0; i < indicesToDelete.size(); i++)
          mask[indicesToDelete[i]] = true;        
    
        mask_it = mask.begin();
        items_it = items.begin();
        while(mask_it != mask.end()){
          if(*mask_it){
            items_it = items.erase(items_it);
            mask_it = mask.erase(mask_it);
          }
          else{
            mask_it++;
            items_it++;
          }
        }
    
        for(int i=0; i<items.size(); i++)
          cout << "items[" << i << "] = " << items[i] << endl;
    
        return 0;
    }
    

    这不是使用大型数据集的快速实现。消除元素后,方法“erase()”需要时间来重新排列向量。