对自定义对象的向量进行排序


248

如何对包含自定义(即用户定义)对象的向量进行排序。
可能应该使用标准STL算法排序以及将对自定义对象中的某个字段(作为排序的键)进行操作的谓词(函数或函数对象)。
我在正确的轨道上吗?


Answers:


365

一个简单的例子使用 std::sort

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};

struct less_than_key
{
    inline bool operator() (const MyStruct& struct1, const MyStruct& struct2)
    {
        return (struct1.key < struct2.key);
    }
};

std::vector < MyStruct > vec;

vec.push_back(MyStruct(4, "test"));
vec.push_back(MyStruct(3, "a"));
vec.push_back(MyStruct(2, "is"));
vec.push_back(MyStruct(1, "this"));

std::sort(vec.begin(), vec.end(), less_than_key());

编辑:作为基里尔五Lyadvinsky指出,而不是提供一个排序谓词,您可以实现operator<MyStruct

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}

    bool operator < (const MyStruct& str) const
    {
        return (key < str.key);
    }
};

使用此方法意味着您可以简单地对向量进行如下排序:

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

Edit2:正如Kappa建议的那样,您还可以通过重载>运算符并稍微改变sort的调用来对向量进行降序排序:

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}

    bool operator > (const MyStruct& str) const
    {
        return (key > str.key);
    }
};

您应该将sort称为:

std::sort(vec.begin(), vec.end(),greater<MyStruct>());

2
您能解释一下为什么在结构less_than_key(在第一个示例中)内联比较函数吗?
克鲁卡

2
另一个问题/注释:如果一个类想在一个类中具有多种排序方法(针对不同的属性),那么重载<运算符的方法可能不是一种选择,对吗?
克鲁卡

5
一个很酷的事情是还提供operator>方法。这将使我们能够按相反的顺序进行排序,例如:std::sort(vec.begin(), vec.end(), greater<MyStruct>())干净而优雅。
卡帕2014年

3
@Bovaz您需要#include <functional>使用“ std :: greater”。
尼克·哈顿

4
@kappa:您可以在其中operator<使用std::sort(vec.begin(), vec.end());或使用的位置,或者std::sort(vec.rbegin(), vec.rend());取决于您要使用升序还是降序。
Pixelchemist's

181

为了覆盖。我提出了一个使用lambda表达式的实现。

C ++ 11

#include <vector>
#include <algorithm>

using namespace std;

vector< MyStruct > values;

sort( values.begin( ), values.end( ), [ ]( const MyStruct& lhs, const MyStruct& rhs )
{
   return lhs.key < rhs.key;
});

C ++ 14

#include <vector>
#include <algorithm>

using namespace std;

vector< MyStruct > values;

sort( values.begin( ), values.end( ), [ ]( const auto& lhs, const auto& rhs )
{
   return lhs.key < rhs.key;
});

21
包含#includes的额外+1-
安妮

3
需要明确的是,这导致了升序。使用>而不是<获取降序。
bhaller

56

您可以将functor用作的第三个参数std::sort,也可以operator<在类中定义。

struct X {
    int x;
    bool operator<( const X& val ) const { 
        return x < val.x; 
    }
};

struct Xgreater
{
    bool operator()( const X& lx, const X& rx ) const {
        return lx.x < rx.x;
    }
};

int main () {
    std::vector<X> my_vec;

    // use X::operator< by default
    std::sort( my_vec.begin(), my_vec.end() );

    // use functor
    std::sort( my_vec.begin(), my_vec.end(), Xgreater() );
}

4
为什么我们需要const在函数签名的末尾添加?
2013年

4
该函数不会更改对象,而是会更改const
Kirill V. Lyadvinsky

如果是这种情况,那么我们为什么要传递“ const X&val”,我认为将值作为const传递给函数会使函数认为它的值不会被更改。
Prashant Bhanarkar '16

1
@PrashantBhanarkar const签名末尾的关键字指定该operator()函数不更改该Xgreater结构的实例(通常可以具有成员变量),而const对于输入值指示仅指定那些输入值是不可变的。
schester

15

可以使用多种方法来实现vector对此类自定义对象或任何其他适用的(可变输入迭代器)范围进行排序X,特别是包括使用标准库算法(例如

由于X已经获得了大多数用于获取元素相对顺序的技术,因此,我将以“为什么”和“何时”使用一些方法开始一些注释。

“最佳”方法将取决于不同的因素:

  1. X对象的范围进行排序是常见的还是罕见的任务(将这些范围在程序中还是在库用户的不同位置进行排序)?
  2. 是所需的排序是“自然的”(预期的)还是类型可以与自身进行比较的多种方式?
  3. 是性能问题还是X对象分类范围应该万无一失?

如果的排序范围X是一项常见的任务,并且期望实现的排序(即X仅包装一个基本值),则可能会导致过载,operator<因为它可以进行排序而没有任何绒毛(例如正确地传递了适当的比较器),并且可以反复产生预期的结果结果。

如果排序是一项常见的任务,或者可能需要在不同的上下文中进行,但是有多个可用于对X对象进行排序的条件,则可以使用Functors(operator()自定义类的重载函数)或Function指针(例如,一个functor / function)用于词法排序,另一种用于自然排序)。

如果类型的排序范围X在其他情况下不常见或不太可能,我倾向于使用lambda而不是使具有更多函数或类型的任何名称空间变得混乱。

如果排序在某种程度上不是“清晰的”或“自然的”,则尤其如此。当您查看就地应用的lambda时,可以很容易地获得排序背后的逻辑,而operator<乍一看却很模糊,您必须向上看定义才能知道将应用什么排序逻辑。

但是请注意,单个operator<定义是单个故障点,而多个lambas是多个故障点,需要更加谨慎。

如果operator<在进行排序/编译排序模板的地方不存在的定义,则在比较对象时可能会迫使编译器进行函数调用,而不是内联排序逻辑,这可能会带来严重的缺点(至少在链接时间优化/代码生成未应用)。

class X使用标准库排序算法实现可比性的方法

std::vector<X> vec_X;std::vector<Y> vec_Y;

1.重载T::operator<(T)operator<(T, T)使用不希望具有比较功能的标准库模板。

任一重载成员operator<

struct X {
  int i{}; 
  bool operator<(X const &r) const { return i < r.i; } 
};
// ...
std::sort(vec_X.begin(), vec_X.end());

或免费operator<

struct Y {
  int j{}; 
};
bool operator<(Y const &l, Y const &r) { return l.j < r.j; }
// ...
std::sort(vec_Y.begin(), vec_Y.end());

2.使用带有自定义比较功能的功能指针作为排序功能参数。

struct X {
  int i{};  
};
bool X_less(X const &l, X const &r) { return l.i < r.i; }
// ...
std::sort(vec_X.begin(), vec_X.end(), &X_less);

3. bool operator()(T, T)为自定义类型创建一个重载,该重载可以作为比较函子传递。

struct X {
  int i{};  
  int j{};
};
struct less_X_i
{
    bool operator()(X const &l, X const &r) const { return l.i < r.i; }
};
struct less_X_j
{
    bool operator()(X const &l, X const &r) const { return l.j < r.j; }
};
// sort by i
std::sort(vec_X.begin(), vec_X.end(), less_X_i{});
// or sort by j
std::sort(vec_X.begin(), vec_X.end(), less_X_j{});

可以使用C ++ 11和模板将这些函数对象定义写得更加通用:

struct less_i
{ 
    template<class T, class U>
    bool operator()(T&& l, U&& r) const { return std::forward<T>(l).i < std::forward<U>(r).i; }
};

在成员i支持下可用于对任何类型进行排序<

4.将匿名闭包(lambda)作为比较参数传递给排序函数。

struct X {
  int i{}, j{};
};
std::sort(vec_X.begin(), vec_X.end(), [](X const &l, X const &r) { return l.i < r.i; });

C ++ 14在其中启用了更通用的lambda表达式:

std::sort(a.begin(), a.end(), [](auto && l, auto && r) { return l.i < r.i; });

可以包装在宏中

#define COMPARATOR(code) [](auto && l, auto && r) -> bool { return code ; }

使普通比较器的创建相当顺利:

// sort by i
std::sort(v.begin(), v.end(), COMPARATOR(l.i < r.i));
// sort by j
std::sort(v.begin(), v.end(), COMPARATOR(l.j < r.j));

2.情况下,您bool X_less(X const &l, X const &r) const { return l.i < r.i; }为比较器编写了代码,但const应删除关键字(因为它不是成员函数)。
PolGraphic's

@PolGraphic:正确-同样在情况1中。
Pixelchemist

@Pixelchemist在不使用std::sort或类似但需要实例化时Compare(例如,当实例化std::set?时)如何使用(4.)lambda方法?
azrdev

1
@azrdev:一个函数模板,它捕获闭包的类型,并将其作为要设置的模板参数传递:template<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }可以像一样使用auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });
Pixelchemist

14

您走在正确的轨道上。 默认情况下std::sortoperator<用作比较功能。因此,为了对对象进行排序,您将不得不重载bool operator<( const T&, const T& )或提供一个执行比较的函子,就像这样:

 struct C {
    int i;
    static bool before( const C& c1, const C& c2 ) { return c1.i < c2.i; }
 };

 bool operator<( const C& c1, const C& c2 ) { return c1.i > c2.i; }

 std::vector<C> values;

 std::sort( values.begin(), values.end() ); // uses operator<
 std::sort( values.begin(), values.end(), C::before );

使用函子的优点是您可以使用具有访问类的私有成员权限的函数。


遗漏了一个:提供成员函数operator <。
xtofl

1
最好使operator<一个类(或结构)的成员,因为全局成员可以使用受保护的成员或私有成员。或者,你应该让结构下的一个朋友
基里尔五Lyadvinsky

5

我很好奇是否可以调用std :: sort的各种方式之间的性能受到可衡量的影响,所以我创建了这个简单的测试:

$ cat sort.cpp
#include<algorithm>
#include<iostream>
#include<vector>
#include<chrono>

#define COMPILER_BARRIER() asm volatile("" ::: "memory");

typedef unsigned long int ulint;

using namespace std;

struct S {
  int x;
  int y;
};

#define BODY { return s1.x*s2.y < s2.x*s1.y; }

bool operator<( const S& s1, const S& s2 ) BODY
bool Sgreater_func( const S& s1, const S& s2 ) BODY

struct Sgreater {
  bool operator()( const S& s1, const S& s2 ) const BODY
};

void sort_by_operator(vector<S> & v){
  sort(v.begin(), v.end());
}

void sort_by_lambda(vector<S> & v){
  sort(v.begin(), v.end(), []( const S& s1, const S& s2 ) BODY );
}

void sort_by_functor(vector<S> &v){
  sort(v.begin(), v.end(), Sgreater());
}

void sort_by_function(vector<S> &v){
  sort(v.begin(), v.end(), &Sgreater_func);
}

const int N = 10000000;
vector<S> random_vector;

ulint run(void foo(vector<S> &v)){
  vector<S> tmp(random_vector);
  foo(tmp);
  ulint checksum = 0;
  for(int i=0;i<tmp.size();++i){
     checksum += i *tmp[i].x ^ tmp[i].y;
  }
  return checksum;
}

void measure(void foo(vector<S> & v)){

ulint check_sum = 0;

  // warm up
  const int WARMUP_ROUNDS = 3;
  const int TEST_ROUNDS = 10;

  for(int t=WARMUP_ROUNDS;t--;){
    COMPILER_BARRIER();
    check_sum += run(foo);
    COMPILER_BARRIER();
  }

  for(int t=TEST_ROUNDS;t--;){
    COMPILER_BARRIER();
    auto start = std::chrono::high_resolution_clock::now();
    COMPILER_BARRIER();
    check_sum += run(foo);
    COMPILER_BARRIER();
    auto end = std::chrono::high_resolution_clock::now();
    COMPILER_BARRIER();
    auto duration_ns = std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count();

    cout << "Took " << duration_ns << "s to complete round" << endl;
  }

  cout << "Checksum: " << check_sum << endl;
}

#define M(x) \
  cout << "Measure " #x " on " << N << " items:" << endl;\
  measure(x);

int main(){
  random_vector.reserve(N);

  for(int i=0;i<N;++i){
    random_vector.push_back(S{rand(), rand()});
  }

  M(sort_by_operator);
  M(sort_by_lambda);
  M(sort_by_functor);
  M(sort_by_function);
  return 0;
}

它的作用是创建一个随机向量,然后测量复制它并对其副本进行排序需要多少时间(并计算一些校验和,以避免过强的死代码消除)。

我正在使用g ++(GCC)7.2.1 20170829(Red Hat 7.2.1-1)进行编译

$ g++ -O2 -o sort sort.cpp && ./sort

结果如下:

Measure sort_by_operator on 10000000 items:
Took 0.994285s to complete round
Took 0.990162s to complete round
Took 0.992103s to complete round
Took 0.989638s to complete round
Took 0.98105s to complete round
Took 0.991913s to complete round
Took 0.992176s to complete round
Took 0.981706s to complete round
Took 0.99021s to complete round
Took 0.988841s to complete round
Checksum: 18446656212269526361
Measure sort_by_lambda on 10000000 items:
Took 0.974274s to complete round
Took 0.97298s to complete round
Took 0.964506s to complete round
Took 0.96899s to complete round
Took 0.965773s to complete round
Took 0.96457s to complete round
Took 0.974286s to complete round
Took 0.975524s to complete round
Took 0.966238s to complete round
Took 0.964676s to complete round
Checksum: 18446656212269526361
Measure sort_by_functor on 10000000 items:
Took 0.964359s to complete round
Took 0.979619s to complete round
Took 0.974027s to complete round
Took 0.964671s to complete round
Took 0.964764s to complete round
Took 0.966491s to complete round
Took 0.964706s to complete round
Took 0.965115s to complete round
Took 0.964352s to complete round
Took 0.968954s to complete round
Checksum: 18446656212269526361
Measure sort_by_function on 10000000 items:
Took 1.29942s to complete round
Took 1.3029s to complete round
Took 1.29931s to complete round
Took 1.29946s to complete round
Took 1.29837s to complete round
Took 1.30132s to complete round
Took 1.3023s to complete round
Took 1.30997s to complete round
Took 1.30819s to complete round
Took 1.3003s to complete round
Checksum: 18446656212269526361

看起来除了传递函数指针以外的所有选项都非常相似,传递函数指针会导致+ 30%的损失。

看起来好像operator <版本慢了约1%(我多次重复测试并且效果仍然存在),这有点奇怪,因为它表明生成的代码是不同的(我缺乏分析--save-临时输出)。



3

在您的课程中,您可以重载“ <”运算符。

class MyClass
{
  bool operator <(const MyClass& rhs)
  {
    return this->key < rhs.key;
  }
}

3

下面是使用lambdas的代码

#include "stdafx.h"
#include <vector>
#include <algorithm>

using namespace std;

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};

int main()
{
    std::vector < MyStruct > vec;

    vec.push_back(MyStruct(4, "test"));
    vec.push_back(MyStruct(3, "a"));
    vec.push_back(MyStruct(2, "is"));
    vec.push_back(MyStruct(1, "this"));

    std::sort(vec.begin(), vec.end(), 
        [] (const MyStruct& struct1, const MyStruct& struct2)
        {
            return (struct1.key < struct2.key);
        }
    );
    return 0;
}

1
    // sort algorithm example
    #include <iostream>     // std::cout
    #include <algorithm>    // std::sort
    #include <vector>       // std::vector
    using namespace std;
    int main () {
        char myints[] = {'F','C','E','G','A','H','B','D'};
        vector<char> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33
        // using default comparison (operator <):
        sort (myvector.begin(), myvector.end());           //(12 32 45 71)26 80 53 33
        // print out content:
        cout << "myvector contains:";
        for (int i=0; i!=8; i++)
            cout << ' ' <<myvector[i];
        cout << '\n';
        system("PAUSE");
    return 0;
    }

1

您可以使用用户定义的比较器类。

class comparator
{
    int x;
    bool operator()( const comparator &m,  const comparator &n )
    { 
       return m.x<n.x;
    }
 }

0

要对向量排序,可以使用中的sort()算法。

sort(vec.begin(),vec.end(),less<int>());

使用的第三个参数可以更大或更小,或者也可以使用任何功能或对象。但是,如果您将第三个参数留空,则默认运算符为<。

// using function as comp
std::sort (myvector.begin()+4, myvector.end(), myfunction);
bool myfunction (int i,int j) { return (i<j); }

// using object as comp
std::sort (myvector.begin(), myvector.end(), myobject);

0
typedef struct Freqamp{
    double freq;
    double amp;
}FREQAMP;

bool struct_cmp_by_freq(FREQAMP a, FREQAMP b)
{
    return a.freq < b.freq;
}

main()
{
    vector <FREQAMP> temp;
    FREQAMP freqAMP;

    freqAMP.freq = 330;
    freqAMP.amp = 117.56;
    temp.push_back(freqAMP);

    freqAMP.freq = 450;
    freqAMP.amp = 99.56;
    temp.push_back(freqAMP);

    freqAMP.freq = 110;
    freqAMP.amp = 106.56;
    temp.push_back(freqAMP);

    sort(temp.begin(),temp.end(), struct_cmp_by_freq);
}

如果compare为假,它将执行“交换”。


用任何语言都不会编译。
LF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.