2010-02-04 8 views
21

mi chiedo se non v'è il supporto in STL per questo:Esiste supporto in C++/STL per l'ordinamento degli oggetti per attributo?

Dire che ho una classe come questa:

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

e un vettore:

vector<Person*> people; 

Vorrei ordinare il vettore di persone della loro età: So che posso farlo nel seguente modo:

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

C'è un modo meno dettagliato per farlo? Sembra eccessivo dover definire un'intera classe solo perché voglio ordinare in base a un 'attributo'. Forse qualcosa del genere?

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

C++ 0x ha funzioni lambda e TR1 aggiunge il '' colpo di testa che rendono questo molto meno inutilmente prolisso. – Omnifarious

+2

+1 molto ben chiesto e risposto. Questo dovrebbe essere il post a cui sono collegati i futuri duplicati. –

+0

@Omnifarious: sei sicuro che ci sia qualcosa in TR1/funzionale che aiuti in questo esempio rispetto a cosa potresti già fare in C++ 03? – Manuel

risposta

20

adattatore generico di comparazione sulla base di attributi utente. Mentre è molto più prolisso la prima volta è riutilizzabile.

// 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

Mi fa male la testa per leggerlo, ma mi piace l'idea. –

+0

Molto bello. Sono stato in grado di estendere questo per usare metodi getter. –

3

Se c'è solo una cosa che si sta sempre andando a voler ordinare le persone di (o se c'è un default ragionevole che si sta andando a voler utilizzare la maggior parte del tempo), ignorare operator< per il People classe da ordinare per questo attributo. Senza un comparatore esplicito, le funzioni di ordinamento STL (e qualsiasi cosa che faccia uso implicito dell'ordine, come insiemi e mappe) useranno lo operator<.

Quando si desidera eseguire l'ordinamento in base a un valore diverso da operator<, il modo in cui viene descritto è l'unico modo per farlo a partire dalla versione corrente di C++ (sebbene il comparatore possa essere solo una funzione regolare, non deve essere un funtore). Lo standard C++ 0x renderà meno dettagliato, consentendo lambda functions.

Se non si è disposti ad attendere C++ 0x, un'alternativa è utilizzare boost::lambda.

+6

L'ordinamento di un vettore vettoriale di People * pointers * non utilizzerà mai People :: operator <() –

+2

Se si desidera ottenere informazioni tecniche, i confronti nella libreria standard non utilizzano l'operatore ', che usa l'operatore' ' per un tipo, e usare quello che vuoi in quella implementazione. –

+0

Sfortunatamente deve essere una funzione autonoma, non un membro della classe. –

0

È possibile avere solo una funzione globale o una funzione statica. Ognuna di queste funzioni globali o statiche si confronta con un attributo. Non c'è bisogno di fare una lezione. Un modo per tenere i papeters per il confronto è usare boost bind, ma bind è utile solo per trovare tutte le classi o confrontare tutte le classi con alcuni parametri associati. Memorizzare i dati tra più elementi è l'unica ragione per creare un funtore.

Modifica: vedi anche aumentare le funzioni lambda, ma sono pratici solo per le funzioni semplici.

11

Non è necessario creare una classe - basta scrivere una funzione:

#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

Basta notare che in molti (la maggior parte?) casi, l'ordinamento utilizzando la funzione finirà per rallentare la corsa. –

+4

Più lento di cosa? Non smistamento? – leeeroy

+3

@leeeroy: esecuzione più lenta rispetto all'utilizzo di un functor. –

1

vedo che dribeas già postato questa idea, ma dal momento che ho già scritto, ecco come devi scrivere un comparatore generico per utilizzare le funzioni 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); 
} 

Usage:

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

penso che potrebbe essere una buona idea di sovraccaricare operator() per i non-puntatori, ma d'altra parte non si poteva typedef i argument_types ereditando da binary_function - che probabilmente non è una grande perdita, dal momento che sarebbe difficile usarlo dove sono comunque necessari, ad esempio, non si può negare comunque il funtore di comparazione.

+0

Poiché lo standard ha già "mem_fn" e "mem_fun", potrebbe essere una buona idea chiamare questo "mem_fn_cmp" invece di "CompareAttribute". – Manuel

+0

Qualunque cosa ti piaccia (mentre è una cosa carina, generalmente finisco per usare boost.bind o simili). – UncleBens

5

Questa non è davvero tanto una risposta in sé, come una risposta alla risposta di AraK al mio commento che l'ordinamento con una funzione anziché un funtore può essere più lento. Ecco un codice di prova (certamente brutto - troppa CnP) che confronta vari tipi di ordinamento: qsort, std :: sorta di vettore contro matrice e std :: sort utilizzando una classe template, una funzione template o una funzione plain per il confronto:

#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; 
} 

compilazione questo con VC++ 9/VS 2008 utilizzando cl /O2b2 /GL sortbench3.cpp, ottengo:

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 

credo che questi cadano abbastanza pulito in tre gruppi: usando sorta con il confronto di default, e utilizzando la classe template prodotto il codice più veloce. L'uso della funzione o della funzione template è chiaramente più lento. L'uso di qsort è (a sorpresa per alcuni) il più lento di tutti, con un margine di 2: 1.

La differenza tra cmp2 e cmp3 sembra derivare interamente dal passaggio per riferimento rispetto al valore - se si modifica cmp2 per ottenere i suoi argomenti in base al valore, la sua velocità corrisponde esattamente a cmp3 (almeno nei miei test). La differenza è che se sapete che il tipo sarà int, passerete quasi certamente per valore, mentre per il generico T, di solito passerete con riferimento const (nel caso in cui sia qualcosa che è più costoso da copiare).

+0

@Jerry Sono d'accordo con te, che usare un funtore è la cosa giusta da fare. Preferisco ovviamente questa soluzione, ma come ho detto, i miei (poveri) test non sono una regola. +1 per mostrare i risultati del test. A proposito, mi dispiace per aver scritto male il tuo nome nel mio primo commento :) – AraK

+0

@AraK: Non sto spingendo troppo l'idea che il functor sia la cosa "giusta" da fare, dato che ci può essere un compromesso, quindi il " la verbosità "di un funtore * può * essere utile. Abbastanza bene sull'ortografia - sembra che avvenga abbastanza regolarmente (anche se più spesso con il mio cognome - quando ero bambino, consegnavo i giornali, e le persone che trovavano che pagare con un assegno trovavano modi veramente creativi per scriverlo ... .) –

+0

C'è ancora una differenza tra cmp3 e precedenti comparatori. cmp3 prende argomenti in base al valore. Potresti provare una funzione cmp4 che contiene un riferimento? –

0

Ho appena provato questo basato su idee UncleBens e david-rodriguez-dribeas.

Questo sembra funzionare (come è) con il mio attuale compilatore. g ++ 3.2.3. Per favore fatemi sapere se funziona su altri compilatori.

#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

Queste risposte sono tutte davvero prolisse anche se adoro l'idea modello! Basta usare le funzioni lambda, rende le cose molto più semplici!

Si potrebbe utilizzare questo:

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