繁体   English   中英

C++/STL 中是否支持按属性对对象进行排序?

[英]Is there support in C++/STL for sorting objects by attribute?

我想知道 STL 是否对此提供支持:

假设我有这样的课程:

class Person
{
public:
  int getAge() const;
  double getIncome() const;
  ..
  ..
};

和一个向量:

vector<Person*> people;

我想按年龄对人的向量进行排序:我知道我可以通过以下方式进行:

class AgeCmp
{
public:
   bool operator() ( const Person* p1, const Person* p2 ) const
   {
     return p1->getAge() < p2->getAge();
   }
};
sort( people.begin(), people.end(), AgeCmp() );

有没有更简洁的方法来做到这一点? 仅仅因为我想根据“属性”进行排序,就必须定义一个完整的类似乎有点过头了。 可能是这样的?

sort( people.begin(), people.end(), cmpfn<Person,Person::getAge>() );

基于成员属性进行比较的通用适配器。 虽然它在第一次可重用时更加冗长。

// Generic member less than
template <typename T, typename M, typename C>
struct member_lt_type 
{
   typedef M T::* member_ptr;
   member_lt_type( member_ptr p, C c ) : ptr(p), cmp(c) {}
   bool operator()( T const & lhs, T const & rhs ) const 
   {
      return cmp( lhs.*ptr, rhs.*ptr );
   }
   member_ptr ptr;
   C cmp;
};

// dereference adaptor
template <typename T, typename C>
struct dereferrer
{
   dereferrer( C cmp ) : cmp(cmp) {}
   bool operator()( T * lhs, T * rhs ) const {
      return cmp( *lhs, *rhs );
   }
   C cmp;
};

// syntactic sugar
template <typename T, typename M>
member_lt_type<T,M, std::less<M> > member_lt( M T::*ptr ) {
   return member_lt_type<T,M, std::less<M> >(ptr, std::less<M>() );
}

template <typename T, typename M, typename C>
member_lt_type<T,M,C> member_lt( M T::*ptr, C cmp ) {
   return member_lt_type<T,M,C>( ptr, cmp );
}

template <typename T, typename C>
dereferrer<T,C> deref( C cmp ) {
   return dereferrer<T,C>( cmp );
}

// usage:    
struct test { int x; }
int main() {
   std::vector<test> v;
   std::sort( v.begin(), v.end(), member_lt( &test::x ) );
   std::sort( v.begin(), v.end(), member_lt( &test::x, std::greater<int>() ) );

   std::vector<test*> vp;
   std::sort( v.begin(), v.end(), deref<test>( member_lt( &test::x ) ) );
}

您不需要创建一个类 - 只需编写一个函数:

#include <vector>
#include <algorithm>
using namespace std;

struct Person {
    int age;
    int getage() const {
        return age;
    }
};

bool cmp( const Person * a, const Person * b ) {
    return a->getage() < b->getage() ;
}

int main() {
    vector <Person*> v;
    sort( v.begin(), v.end(), cmp );
}

这本身并不是一个真正的答案,作为对 AraK 对我评论的回复的回复,即使用函数而不是仿函数进行排序可能会更慢。 下面是一些比较各种排序的(诚然丑陋——CnP 太多)测试代码:qsort、std::sort of vector vs. array,以及 std::sort 使用模板类、模板函数或普通函数进行比较:

#include <vector>
#include <algorithm>
#include <stdlib.h>
#include <time.h>

int compare(void const *a, void const *b) {
    if (*(int *)a > *(int *)b)
        return -1;
    if (*(int *)a == *(int *)b)
        return 0;
    return 1;
}

const int size = 200000;

typedef unsigned long ul;

void report(char const *title, clock_t ticks) { 
    printf("%s took %f seconds\n", title, ticks/(double)CLOCKS_PER_SEC);
}

void wait() { 
    while (clock() == clock())
        ;
}

template <class T>
struct cmp1 { 
    bool operator()(T const &a, T const &b) { 
        return a < b;
    }
};

template <class T>
bool cmp2(T const &a, T const &b) { 
    return a<b;
}

bool cmp3(int a, int b) { 
    return a<b;
}

int main(void) {
    static int array1[size];
    static int array2[size];

    srand(time(NULL));

    for (int i=0; i<size; i++) 
        array1[i] = rand();

    const int iterations = 100;

    clock_t total = 0;

    for (int i=0; i<iterations; i++) { 
        memcpy(array2, array1, sizeof(array1));
        wait();
        clock_t start = clock();
        qsort(array2, size, sizeof(array2[0]), compare);
        total += clock()-start;
    }
    report("qsort", total);

    total = 0;
    for (int i=0; i<iterations; i++) {
        memcpy(array2, array1, sizeof(array1));
        wait();
        clock_t start = clock();
        std::sort(array2, array2+size);
        total += clock()- start;
    }
    report("std::sort (array)", total);

    total = 0;
    for (int i=0; i<iterations; i++) {
        memcpy(array2, array1, sizeof(array1));
        wait();
        clock_t start = clock();
        std::sort(array2, array2+size, cmp1<int>());
        total += clock()- start;
    }
    report("std::sort (template class comparator)", total);

    total = 0;
    for (int i=0; i<iterations; i++) {
        memcpy(array2, array1, sizeof(array1));
        wait();
        clock_t start = clock();
        std::sort(array2, array2+size, cmp2<int>);
        total += clock()- start;
    }
    report("std::sort (template func comparator)", total);

    total = 0;
    for (int i=0; i<iterations; i++) {
        memcpy(array2, array1, sizeof(array1));
        wait();
        clock_t start = clock();
        std::sort(array2, array2+size, cmp3);
        total += clock()- start;
    }
    report("std::sort (func comparator)", total);

    total = 0;
    for (int i=0; i<iterations; i++) {
        std::vector<int> array3(array1, array1+size);
        wait();
        clock_t start = clock();
        std::sort(array3.begin(), array3.end());
        total += clock()-start;
    }
    report("std::sort (vector)", total);

    return 0;
} 

使用cl /O2b2 /GL sortbench3.cpp用 VC++ 9/VS 2008 编译它,我得到:

qsort took 3.393000 seconds
std::sort (array) took 1.724000 seconds
std::sort (template class comparator) took 1.725000 seconds
std::sort (template func comparator) took 2.725000 seconds
std::sort (func comparator) took 2.505000 seconds
std::sort (vector) took 1.721000 seconds

我相信这些相当干净地分为三组:使用带有默认比较的排序,以及使用模板类生成最快的代码。 使用函数或模板函数显然更慢。 使用 qsort 是(对某些人来说令人惊讶的)最慢的,大约为 2:1。

cmp2 和 cmp3 之间的差异似乎完全源于按引用传递与值传递——如果您更改 cmp2 以按值获取其参数,它的速度与 cmp3 完全匹配(至少在我的测试中)。 不同之处在于,如果您知道类型将是int ,那么您几乎肯定会通过值传递,而对于泛型T ,您通常会通过 const 引用传递(以防万一它的复制成本更高)。

如果您只想按一件事对人员进行排序(或者如果有一个合理的默认值,您大部分时间都希望使用该默认值),请覆盖operator<以便People类按此属性进行排序. 如果没有显式比较器,STL 排序函数(以及任何隐式使用排序的函数,如集合和映射)将使用operator<

当您想按operator<以外的其他内容进行排序时,您描述的方式是当前 C++ 版本的唯一方法(尽管比较器可以只是一个常规函数;它不必是函子)。 C++0x 标准通过允许lambda 函数来减少冗长。

如果您不愿意等待 C++0x,另一种方法是使用boost::lambda

尽管我喜欢模板的想法,但这些答案都非常冗长! 只需使用 lambda 函数,它就让事情变得简单多了!

你可以用这个:

sort( people.begin(), people.end(), []( Person a, Person b ){ return a.age < b.age; } );

我看到 dribeas 已经发布了这个想法,但是因为我已经写了它,所以这里是你如何编写一个通用比较器来使用 getter 函数的方法。

#include <functional>

template <class Object, class ResultType>
class CompareAttributeT: public std::binary_function<const Object*, const Object*, bool>
{
    typedef ResultType (Object::*Getter)() const;
    Getter getter;
public:
    CompareAttributeT(Getter method): getter(method) {}
    bool operator()(const Object* lhv, const Object* rhv) const
    {
        return (lhv->*getter)() < (rhv->*getter)();
    }
};

template <class Object, class ResultType>
CompareAttributeT<Object, ResultType> CompareAttribute(ResultType (Object::*getter)() const)
{
    return CompareAttributeT<Object, ResultType>(getter);
}

用法:

std::sort(people.begin(), people.end(), CompareAttribute(&Person::getAge));

我认为为非指针重载operator()可能是一个好主意,但是不能通过从binary_function继承来对 argument_types 进行类型定义——这可能不是一个很大的损失,因为在那些地方很难使用它无论如何都需要,例如,无论如何都不能否定比较函子。

您可以只拥有一个全局函数或一个静态函数。 这些全局或静态函数中的每一个都与一个属性进行比较。 不需要上课。 保存论文进行比较的一种方法是使用 boost bind,但 bind 仅用于查找所有类或将所有类与某个绑定参数进行比较。 跨多个元素存储数据是制作仿函数的唯一原因。

编辑:另见 boost lambda 函数,但它们仅适用于简单函数。

我只是根据 UncleBens 和 david-rodriguez-dribeas 的想法尝试了这个。

这似乎与我当前的编译器一起工作(原样)。 g++ 3.2.3。 请让我知道它是否适用于其他编译器。

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

class Person
{
public:
    Person( int _age )
        :age(_age)
    {
    }
    int getAge() const { return age; }
private:
    int age;
};

template <typename T, typename ResType>
class get_lt_type
{
    ResType (T::*getter) () const;
public:
    get_lt_type(ResType (T::*method) () const ):getter(method) {}
    bool operator() ( const T* pT1, const T* pT2 ) const
    {
        return (pT1->*getter)() < (pT2->*getter)();
    }
};

template <typename T, typename ResType>
get_lt_type<T,ResType> get_lt( ResType (T::*getter) () const ) {
    return get_lt_type<T, ResType>( getter );
}

int main() {
    vector<Person*> people;
    people.push_back( new Person( 54 ) );
    people.push_back( new Person( 4 ) );
    people.push_back( new Person( 14 ) );

    sort( people.begin(), people.end(), get_lt( &Person::getAge) );

    for ( size_t i = 0; i < people.size(); ++i )
    {
        cout << people[i]->getAge() << endl;
    }
    // yes leaking Persons
    return 0;
}

从 C++20 开始,您可以使用投影直接执行此操作。 投影可以应用于排序范围的每个元素:

#include <algorithm>
#include <vector>

std::vector<Person> persons;
std::ranges::sort(persons, {}, &Person::getAge);

std::vector<Person*> personPtrs;
std::ranges::sort(personPtrs, {}, &Person::getAge);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM