2010-02-04 120 views
21

我不知道是否有支持STL此:在C++/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>()); 
+1

的C++ 0x具有lambda函数和TR1添加''头使这个少了很多不必要的冗长。 – Omnifarious 2010-02-04 20:15:09

+2

+1非常好的问及回答。这应该是未来的dups所关联的职位。 – 2010-02-04 20:33:51

+0

@Omnifarious:你确定TR1/functional中有一些东西可以帮助你在这个例子中说明你已经可以在C++ 03中做些什么吗? – Manuel 2010-02-04 20:44:57

回答

20

通用适配器为将基于成员属性。虽然这是第一次可重复使用,但它更加冗长。

// 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))); 
} 
+1

让我的头受伤阅读它,但我喜欢这个想法。 – 2010-02-04 21:36:12

+0

非常酷。我能够扩展这个使用getter方法。 – 2010-02-05 15:14:27

3

如果有你曾经会想通过(或者,如果有,你会想用大部分的时间合理的默认值),覆盖operator<People的人分开,只有一件事类通过此属性进行排序。没有明确的比较器,STL排序函数(以及任何隐式使用排序的东西,比如集合和映射)将使用operator<

如果您想按operator<以外的值进行排序,您所描述的方式是从当前版本的C++开始执行的唯一方式(尽管比较器可能只是常规函数;它不必是一个函子)。 C++ 0x标准通过允许lambda functions可以减少冗余。

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

+6

对People *指针*的矢量排序进行排序将永远不会使用People :: operator <() – 2010-02-04 20:09:29

+2

如果您想要获取技术,则标准库中的比较不要使用operator <直接使用 - 它们使用std :: less ',默认使用'operator <'。但是,您可以专注于某个类型的'std :: less ',并在该实现中使用任何您想要的内容。 – 2010-02-04 20:15:44

+0

不幸的是它必须是一个独立的功能,而不是该类的成员。 – 2010-02-04 20:27:54

0

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

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

11

你并不需要创建一个类 - 只写一个函数:

#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); 
} 
+0

请注意,在许多(最?)情况下,使用该函数的排序将最终运行得更慢。 – 2010-02-04 20:20:09

+4

运行速度比什么慢?没有排序? – leeeroy 2010-02-04 20:25:28

+3

@leeeroy:比使用函子运行速度慢。 – 2010-02-04 20:28:37

1

我看到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继承的typedef的argument_types - 这可能不是这是一个很大的损失,因为无论如何它都很难使用它,例如,无论如何,只是无法否定比较函子。

+0

由于该标准已经具有“mem_fn”和“mem_fun”,所以调用此“mem_fn_cmp”而不是“CompareAttribute”可能是一个好主意。 – Manuel 2010-02-05 09:43:34

+0

无论你喜欢什么(虽然这是一件好事,但我通常最终只会使用boost.bind或类似的东西)。 – UncleBens 2010-02-05 15:37:26

5

这其实不是一个真正的答案本身,作为对AraK回复我的评论的回复,即使用函数而不是函数进行排序可能会更慢。这里有一些(确实是丑陋的 - 太多的CnP)测试代码,比较各种排序:qsort,std ::向量与数组的排序,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引用(以防万一它的复制成本更高)。

+0

@Jerry我同意你的看法,使用函数是正确的。当然,我更喜欢这个解决方案,但正如我所说,我的(可怜的)测试不是一个规则。 +1以显示您的测试结果。顺便说一句,对不起,在我的第一个评论中拼错你的名字:) – AraK 2010-02-04 21:25:28

+0

@AraK:我并没有太多推动这个想法,即函子是“正确”的事情,因为可以进行折衷,所以“一个仿函数的详细程度*可能是值得的。对拼写来说很正常 - 这似乎是经常发生的(尽管更多时候以我的姓氏为例) - 当我还是个孩子的时候,我发送了报纸,而人们发现用支票付款发现了真正有创意的拼写方式。 ) – 2010-02-04 21:40:27

+0

cmp3和以前的比较器还有一个区别。 cmp3通过值获取参数。你可以尝试一个函数cmp4,通过引用获取整数吗? – 2010-02-05 01:16:56

0

我刚刚根据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; 
} 
1

这些答案都非常详细,虽然我喜欢模板的想法!只需使用lambda函数,它使事情变得更加简单!

你可以只使用此:

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