在给定另一个向量的向量中删除元素的最佳方法是什么?
我提出了以下代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void remove_elements(vector<int>& vDestination, const vector<int>& vSource)
{
if(!vDestination.empty() && !vSource.empty())
{
for(auto i: vSource) {
vDestination.erase(std::remove(vDestination.begin(), vDestination.end(), i), vDestination.end());
}
}
}
int main()
{
vector<int> v1={1,2,3};
vector<int> v2={4,5,6};
vector<int> v3={1,2,3,4,5,6,7,8,9};
remove_elements(v3,v1);
remove_elements(v3,v2);
for(auto i:v3)
cout << i << endl;
return 0;
}
这里的输出将是:
7
8
9
答案 0 :(得分:6)
我的版本如下,我在向量erase
的所有元素移动到vSource
的末尾之后仅应用std::remove
并跟踪指向结尾的指针向量vDestination
不会迭代它。
void remove_elements(vector<int>& vDestination, const vector<int>& vSource)
{
auto last = std::end(vDestination);
std::for_each(std::begin(vSource), std::end(vSource), [&](const int & val) {
last = std::remove(std::begin(vDestination), last, val);
});
vDestination.erase(last, std::end(vDestination));
}
见coliru:http://coliru.stacked-crooked.com/a/6e86893babb6759c
<强>更新强>
这是一个模板版本,因此您不关心容器类型:
template <class ContainerA, class ContainerB>
void remove_elements(ContainerA & vDestination, const ContainerB & vSource)
{
auto last = std::end(vDestination);
std::for_each(std::begin(vSource), std::end(vSource), [&](typename ContainerB::const_reference val) {
last = std::remove(std::begin(vDestination), last, val);
});
vDestination.erase(last, std::end(vDestination));
}
注意强>
此版本适用于没有任何约束的矢量,如果您的矢量已排序,您可以采取一些快捷方式,避免在矢量上反复删除每个元素。
答案 1 :(得分:3)
如果您的向量始终排序,则可以使用set_difference
:
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
void remove_elements(std::vector<int>& vDestination, const std::vector<int>& vSource)
{
std::vector<int> result;
std::set_difference(vDestination.begin(), vDestination.end(), vSource.begin(), vSource.end(), std::back_inserter(result));
vDestination.swap(result);
}
int main()
{
std::vector<int> v1={1,2,3};
std::vector<int> v2={4,5,6};
std::vector<int> v3={1,2,3,4,5,6,7,8,9};
remove_elements(v3,v1);
remove_elements(v3,v2);
for(auto i:v3)
std::cout << i << '\n';
}
如果不是要求,那么输出范围不应该超出任何输入范围,我们甚至可以避免额外的矢量。您可以滚动自己的set_difference
版本,允许在vDestination.begin()
开始的范围内输出,但它超出了此答案的范围。
答案 2 :(得分:3)
我认为 best 你的意思是最快的。由于它是关于效率的问题,我执行了一个简单的基准来比较几种算法的效率。请注意,它们略有不同,因为问题有点不明确 - 出现的问题(以及对基准测量的假设)是:
vDestination
包含vSource
中的所有元素? (假设:不)vDestination
或vSource
中是否允许重复? (假设:是的,两者兼而有之)vDestination
中的每个元素都等于vSource
中的任何元素,或者只有一对一的元素,那么vDestination
和vSource
的大小以某种方式限制的?其中一个总是更大还是多更大? (几个案例测试)如您所见,算法会有几点不同,因此,您可以猜测,最佳算法将取决于您的使用案例。比较算法包括:
基于集合的算法:
std::unordered_set<int> elems(vSource.begin(), vSource.end());
auto i = destination.begin();
auto target = destination.end();
while(i <= target) {
if(elems.count(*i) > 0)
std::swap(*i, *(--target));
else
i++;
}
destination.erase(target, destination.end());
计算算法:
std::unordered_map<int, int> counts;
counts.max_load_factor(0.3);
counts.reserve(destination.size());
for(auto v: destination) {
counts[v]++;
}
for(auto v: source) {
counts[v]--;
}
auto i = destination.begin();
for(auto k: counts) {
if(k.second < 1) continue;
i = std::fill_n(i, k.second, k.first);
}
destination.resize(std::distance(destination.begin(), i));
使用Celero库执行基准测试程序,如下所示:
n
中生成int
伪随机n
s {10,100,1000,10000, 20000, 200000}
并将其置于vector
m
)复制到第二个vector
(来自集合{0.01, 0.1, 0.2, 0.4, 0.6, 0.8}
的分数,最少1个元素)只有算法3,5和6才能在大于10 000个元素的数据集上执行,因为其余部分需要我很长时间才能轻松测量(随意自行完成)。
长话短说:如果您的向量包含少于1000个元素,请选择您喜欢的元素。如果它们更长 - 依赖vSource
的大小。如果它低于vDestination
的50% - 选择基于集合的算法,如果还有更多 - 对它们进行排序并选择@ Revolver_Ocelot的解决方案(它们约为60%,基于集合的vSource
比vDestination
的1%大小快2倍以上。请不要依赖订单或提供从头开始排序的向量 - 要求订购必须保持相同,这将大大降低流程。用例,编译器,标志和硬件的基准测试。我已经附加了我的基准测试链接,以防你想重现它们。
GitHub上提供了完整的结果(文件vector-benchmarks.csv
)以及基准代码(文件tests/benchmarks/vectorRemoval.cpp
)here。
请记住,这些是我在计算机,编译器等上获得的结果 - 在您的情况下,它们会有所不同(特别是在一个算法优于另一个算法时)。
我在Fedora 24上使用GCC 6.1.1和-O3
,在VirtualBox之上。
答案 3 :(得分:1)
可以用STL编写为:
void remove_elements(vector<int>& vDestination, const vector<int>& vSource)
{
const auto isInSource = [&](int e) {
return std::find(vSource.begin(), vSource.end(), e) != vSource.end();
};
vDestination.erase(
std::remove_if(vDestination.begin(), vDestination.end(), isInSource),
vDestination.end());
}
如果vSource
已排序,您可以将std::find
替换为std::binary_search
。