如何有效地删除给定另一个向量的向量中的元素

时间:2016-08-25 07:38:21

标签: c++ algorithm c++11 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

4 个答案:

答案 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中的所有元素? (假设:不)
  • vDestinationvSource中是否允许重复? (假设:是的,两者兼而有之)
  • 结果向量中元素的顺序是否重要? (两种测试案例的算法)
  • 如果vDestination中的每个元素都等于vSource中的任何元素,或者只有一对一的元素,那么
  • 是否应该删除它? (假设:是的,两者兼而有之)
  • vDestinationvSource的大小以某种方式限制的?其中一个总是更大还是更大? (几个案例测试)
  • 在评论中已经解释过,矢量不需要排序,但我已经包含了这一点,因为它不能立即从问题中看到(没有排序)假设在任一矢量中)

如您所见,算法会有几点不同,因此,您可以猜测,最佳算法将取决于您的使用案例。比较算法包括:

  1. 原始的(提议的) - 基线
  2. 在@dkg回答
  3. 中提出
  4. 在@Revolver_Ocelot中提出了答案+附加排序(算法所需)和结果预留空间 向量
  5. 在@ Jarod42中提出答案
  6. 基于集合的算法(下面介绍 - 主要是@ Jarod42算法的优化)
  7. 计算算法(见下文)
  8. 基于集合的算法:

    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库执行基准测试程序,如下所示:

    1. 在集n中生成int伪随机n s {10,100,1000,10000, 20000, 200000}并将其置于vector
    2. 将这些整数的一小部分(m)复制到第二个vector(来自集合{0.01, 0.1, 0.2, 0.4, 0.6, 0.8}的分数,最少1个元素)
    3. 启动计时器
    4. 执行删除程序
    5. 停止计时器
    6. 只有算法3,5和6才能在大于10 000个元素的数据集上执行,因为其余部分需要我很长时间才能轻松测量(随意自行完成)。

      长话短说:如果您的向量包含少于1000个元素,请选择您喜欢的元素。如果它们更长 - 依赖vSource的大小。如果它低于vDestination的50% - 选择基于集合的算法,如果还有更多 - 对它们进行排序并选择@ Revolver_Ocelot的解决方案(它们约为60%,基于集合的vSourcevDestination的1%大小快2倍以上。请不要依赖订单或提供从头开始排序的向量 - 要求订购必须保持相同,这将大大降低流程。用例,编译器,标志和硬件的基准测试。我已经附加了我的基准测试链接,以防你想重现它们。

      GitHub上提供了完整的结果(文件vector-benchmarks.csv)以及基准代码(文件tests/benchmarks/vectorRemoval.cpphere

      请记住,这些是我在计算机,编译器等上获得的结果 - 在您的情况下,它们会有所不同(特别是在一个算法优于另一个算法时)。

      我在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