合并,排序和删除重复的排序向量和未排序的向量

时间:2018-03-30 21:29:40

标签: c++ performance sorting merge duplicates

流程说明

我的代码中相对较慢的部分包括合并两个向量v1v2v1通常比v2长2到1000倍。 v1已排序且不包含任何重复项,而v2未排序且可能包含重复项。

该过程包括使v1等于我们删除所有重复项的v1v2的合并排序结果。应从所有内容中清除v2例如,给定输入

v1 = {1,2,5,6,9,10};
v2 = {3,7,5,3}

流程必须输出

v1 = {1,2,3,5,6,7,9,10};
v2 = {};

虽然v1v2的元素不是原始数据,因此移动语义可能会提高性能。

问题

执行此过程的最快方法是什么?

对象类型

我们首先定义这个类。请注意,重复项的排序/删除只能基于属性a来完成,因为从该类的运算符方法的定义中可以看出它。

class A
{
public:
  int a;
  int b;
  int c;
  A(int i1, int i2, int i3):a(i1),b(i2),c(i3){}
  bool operator==(const A& other) const
  {
    return a == other.a;
  }
  bool operator>(const A& other) const
  {
    return a > other.a;
  }
  bool operator<(const A& other) const
  {
    return a < other.a;
  }
};

三种可能的解决方案

我还没有尝试调试以下功能,所以我希望我没有搞砸到这里。

void f1(std::vector<A>& v1, std::vector<A>& v2 )
{

  v1.insert(
    v1.end(),
    make_move_iterator(v2.begin()),
    make_move_iterator(v2.end())
  );
  v2.clear();

  std::sort(v1.begin(), v1.end());
  v1.erase(
        unique(
            v1.begin(),
            v1.end()
        ),
        v1.end()
    );
}

void f2(std::vector<A>& v1, std::vector<A>& v2 )
{

  std::sort(v2.begin(), v2.end());
  v2.erase(
        unique(
            v2.begin(),
            v2.end()
        ),
        v2.end()
    );

  v1.insert(
    v1.end(),
    make_move_iterator(v2.begin()),
    make_move_iterator(v2.end())
  );
  v2.clear();

  std::sort(v1.begin(), v1.end());
  v1.erase(
        unique(
            v1.begin(),
            v1.end()
        ),
        v1.end()
    );
}

void f3(std::vector<A>& v1, std::vector<A>& v2 )
{

  std::sort(v2.begin(), v2.end());

  std::vector<A> tmp;

  std::merge(v1.begin(),v1.end(),v2.begin(),v2.end(),std::back_inserter(tmp));

  v1 = std::move(tmp);
  v1.erase(
        unique(
            v1.begin(),
            v1.end()
        ),
        v1.end()
    );

  v2.clear();

}

基准

我对这三个功能进行了基准测试

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <ctime>

void createVector(std::vector<A>& v1, std::vector<A>& v2)
{
  int n1 = 10000;
  int n2 = 500;

  std::mt19937 mt{};
  std::uniform_int_distribution<int> dist(0,v1.size() * 100);
  for (int i = 0 ; i < n1; i++)
  {
    v1.push_back({dist(mt),dist(mt),dist(mt)});
  }
  for (int i = 0 ; i < n2; i++)
  {
    v2.push_back({dist(mt),dist(mt),dist(mt)});
  }

  // sort and remove duplicates of v1 only
  std::sort(v1.begin(), v1.end());
  v1.erase(
        unique(
            v1.begin(),
            v1.end()
        ),
        v1.end()
    );
}

std::vector<long double> benchmark ()
{
  std::vector<long double> toReturn;
  std::vector<A> v1;
  std::vector<A> v2;
  std::clock_t before;
  std::clock_t after;


  createVector(v1,v2);
  before = clock();
  f1(v1,v2);
  after = clock();
  toReturn.push_back(((long double) after - (long double) before) /  (long double) CLOCKS_PER_SEC);

  createVector(v1,v2);
  before = clock();
  f2(v1,v2);
  after = clock();
  toReturn.push_back(((long double) after - (long double) before) /  (long double) CLOCKS_PER_SEC);

  createVector(v1,v2);
  before = clock();
  f3(v1,v2);
  after = clock();
  toReturn.push_back(((long double) after - (long double) before) /  (long double) CLOCKS_PER_SEC);

  return toReturn;
}

int main ()
{
  int nbRepeats = 500;
  std::vector<long double> times(3,0.0);
  for (int i = 0 ; i < nbRepeats ;i++)
  {
    auto b = benchmark();

    for (int fun = 0 ; fun < b.size() ;fun++)
      times[fun] = b[fun];
  }

  for (int fun = 0 ; fun < times.size() ;fun++)
      std::cout << "f"<< fun + 1 << ": " << times[fun] / nbRepeats << "\n";

  return 0;
}

输出

f1: 8e-10
f2: 6e-10
f3: 1.2e-09

有没有更快的解决方案?

1 个答案:

答案 0 :(得分:0)

最快的方式将取决于......不可思议的因素。在典型负载下,您需要在实际机器类型上对实际应用程序进行基准测试。

版本f3可以改进,因此它不会扩展v1,这可能会导致重新分配。

using namespace std;

using A = int; // Or whatever
void f4( vector<A>& v1,  vector<A>& v2) {
    sort(v2.begin(), v2.end()); 
    vector<A> ret(v1.size() + v2.size());
    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), ret.begin());
    ret.erase( unique( ret.begin(), ret.end() ), ret.end() );
    swap(v1, ret);
}

我将从我的个人存储中提交合并例程。它在适当的时候使用移动语义。

namespace dj {
 // Move a range of values
template<class Ptr, class Ptr2>
Ptr2 move(Ptr src, Ptr end, Ptr2 dest) {
    using value_type = typename std::remove_reference<decltype(*src)>::type;
    if constexpr (std::is_trivially_copyable_v<value_type>) {
        return std::copy(src, end, dest);
    } else {
        while (src != end) {
            *dest = std::move(*src);
            ++src; ++dest;
        }
    }
    return dest;
}

// Merge using move-semantics
template<class inPtr, class outPtr, class Prd>
outPtr merge(inPtr first1, inPtr last1,
    inPtr first2, inPtr last2,
    outPtr d_first,
    Prd comp)
{
    using value_type = typename 
    std::remove_reference<decltype(*first1)>::type;
    if constexpr (std::is_trivially_copyable_v<value_type>) {
        return std::merge(first1, last1, first2, last2, d_first, comp);
    } else {
        for (; first1 != last1; ++d_first) {
            if (first2 == last2) {
                return dj::move(first1, last1, d_first);
            }
            if (comp(*first2 , *first1)) {
                *d_first = std::move(*first2);
                ++first2;
            } else {
                *d_first = std::move(*first1);
                ++first1;
            }
        }
        return dj::move(first2, last2, d_first);
    }
}
}
using namespace std;

using A = int; // Or whatever
void f4( vector<A>& v1,  vector<A>& v2) {
    sort(v2.begin(), v2.end()); 
    vector<A> ret(v1.size() + v2.size());
    dj::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), ret.begin(), less<>());
    ret.erase( unique( ret.begin(), ret.end() ), ret.end() );
    swap(v1, ret);
}