2010-02-04 118 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; });