我有一个项目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
中的所有其他索引?
一些想法是:
items
复制到新的向量中,如果索引位于indicesToDelete
items
,对于每次删除,减少indicesToDelete
中索引较大的所有项目。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;
}
答案 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_if
和vector::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)
以下是此问题的解决方案,它保持原始“项目”的顺序:
以下是代码示例:
#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()”需要时间来重新排列向量。