如何有效地从给定另一个向量的向量中删除元素
How to efficiently delete elements from a vector given an another vector
从给定另一个向量中删除元素的最佳方法是什么?
我写了下面的代码:
#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
我的版本如下,我只在向量vSource
的所有元素被std::remove
移动到末尾后应用erase
,并跟踪指向向量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));
}
注意
这个版本适用于没有任何约束的向量,如果你的向量是排序的,你可以采取一些快捷方式,避免迭代遍历向量来删除每个元素。
我认为你所说的最好的是指最快的。由于这是一个关于效率的问题,我执行了一个简单的基准来比较几种算法的效率。请注意,它们有一点不同,因为问题有点不明确—出现的问题(以及基准测试的假设)如下:
- 是否保证
vDestination
包含vSource
的所有元素?(假设:没有) - 在
vDestination
或vSource
中允许重复吗?(假设:是的,在两个) - 结果向量中元素的顺序是否重要?(测试两种情况的算法) 如果
-
vDestination
和vSource
的大小是有界的吗?它们中的一个总是更大还是大得多?(已测试的几个案例) - 在评论中已经解释了向量不需要排序,但我已经包括了这一点,因为它不是立即从问题中可见(没有排序假设在两个向量中)
vDestination
中的每个元素与vSource
中的任何元素相等,或者只是一对一的,那么应该删除它们吗?(假设:是的,在两个)- 原始 -基线@dkg回答中提出的
- 在@Revolver_Ocelot答案中提出+额外排序(算法要求)和结果的预预留空间向量
- 在@Jarod42回答中提出
- 基于集合的算法(如下所示-主要是@Jarod42算法的优化)
- 计数算法(见下文)
基于集合的算法:
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
s (n
在{10,100,1000,10000, 20000, 200000}
集合中)并将它们放入vector
- 复制一个分数(
m
)到第二个vector
(分数从set{0.01, 0.1, 0.2, 0.4, 0.6, 0.8}
, min. 1元素) - 开始定时器
- 执行删除程序
- 停止计时器
只有算法3、5和6在大于10,000个元素的数据集上执行,因为其余的算法需要很长时间才能让我舒服地测量(请随意自己做)。
长话短说:如果你的向量包含少于1000个元素,选择你喜欢的。如果它们更长-依赖于vSource
的大小。如果小于vDestination
的50% -选择基于集合的算法,如果大于-对它们进行排序并选择@Revolver_Ocelot的解决方案(它们在60%左右,当vSource
的大小为vDestination
的1%时,基于集合的速度超过2倍)。请不要依赖于顺序或提供一个从一开始就排序的向量——要求顺序必须保持不变会大大降低过程的速度。对你的用例、编译器、标志和硬件进行基准测试。我附上了我的基准测试的链接,以防你想复制它们。
完整的结果(文件vector-benchmarks.csv
)可在GitHub上与基准测试代码(文件tests/benchmarks/vectorRemoval.cpp
)一起获得。
请记住,这些是我在我的计算机上获得的结果,我的编译器等-在你的情况下,它们会有所不同(特别是当涉及到一个算法比另一个更好的点时)。
我在Fedora 24上使用了GCC 6.1.1和-O3
,在VirtualBox之上。
如果你的向量总是排序的,你可以使用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()
开始的范围内输出,但它超出了这个答案的范围。
可以用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
- 迭代时从向量和内存中删除对象
- 如何在向量中删除 std::function<void()>?
- 移除和删除与向量中的条件匹配的指针
- 比较 2 个向量并从第二个向量中删除在第一个 - c++ 中找不到的元素
- 为什么通过 vector<reference_wrapper> 的元素删除引用的值<T>不会使向量无效?
- 如何在元组初始化向量中删除样板?
- 如何删除包含其他向量的向量?
- 从列表向量中删除无法按预期工作
- 从数组中删除非唯一值、保持顺序和不使用向量的最佳方法?
- 如何删除除 ArduinoSTL 的向量函数之外的所有函数
- 如何在具有特定条件的向量中删除所有元组?
- 删除指向抽象类的指针向量
- 从自定义数据类型向量中删除重复元素
- 防止我的向量在调用它的函数结束时被删除
- 从ints向量删除int的问题
- 使用find_if从向量删除所有偶数数字
- 崩溃,如果使用向量删除了一个嵌入式容器的destructor中删除char*成员
- 从向量删除重复元素
- 基元向量删除引发异常
- 如何使用指向每个元素的指针向量删除qcheckbox数组?