2009-02-28 21 views
4

Je rencontre fréquemment des situations, en particulier avec le tri en C++, où je compare une série de champs afin de comparer une structure plus grande. Un exemple simplifié:Comment structurez-vous vos fonctions de comparaison?

struct Car{ 
    Manufacturer make; 
    ModelName model; 
    Year year; 
}; 

bool carLessThanComparator(const Car & car1, const Car & car2){ 
    if(car1.make < car2.make){ 
     return true; 
    }else if(car1.make == car2.make){ 
     if(car1.model < car2.model){ 
      return true; 
     }else if(car1.model == car2.model){ 
      if(car1.year < car2.year){ 
       return true; 
      } 
     } 
    } 

    return false; 
} 

Mon approche instinctive semble lourde, en particulier pour plus de 3 champs. Comment structureriez-vous cette série de comparaisons en C++? Les autres langues fournissent-elles une syntaxe plus succincte ou élégante?

+0

Style personnel - J'implémenterais une fonction (ou méthode) de type 'strcmp()' qui renvoie 0 si a est égal à b, 1 si a est supérieur à b, et -1 si a est inférieur à b, puis définir les opérateurs de comparaison (si vous voulez) et/ou les méthodes en fonction de la fonction de comparaison. Ensuite, nous aurions juste 'bool carLessThanComparator (const Car & voiture1, const Car & car2) {return cmp (car1, car2) == -1; } ' –

Répondre

5

Eh bien, si votre fonction frappe un retour dans le si clause, il n'y a pas besoin d'un autre explicite, car il aurait déjà renflouées. Cela peut sauver sur la « vallée tiret »:

bool carLessThanComparator(const Car & car1, const Car & car2) { 
    if(car1.make < car2.make) 
     return true; 

    if (car1.make != car2.make) 
     return false; 

    if(car1.model < car2.model) 
     return true; 

    if(car1.model != car2.model) 
     return false; 

    if(car1.year < car2.year) 
     return true; 

    return false; 
} 

J'aime approche court-circuit lispien de MarkusQ ainsi.

+0

Pourquoi ne pas simplement" retourner car1.year janm

+1

Juste pour la symétrie - Je voulais que l'échantillon de code soit aussi clair que possible. En pratique, je ferais ce que vous avez dit, ou vraiment je le coderais comme Markus l'a fait. – Crashworks

+0

Merci beaucoup. Les gens ont besoin d'apprendre de cela. – ChaosPandion

2
bool carLessThanComparator(const Car & car1, const Car & car2){ 
    return (
     (car1.make < car2.make ) or ((car1.make == car2.make ) and 
     (car1.model < car2.model) or ((car1.model == car2.model) and 
     (car1.year < car2.year ) 
    ))); 

- MarkusQ

4

Si cela se produit beaucoup vous pouvez mettre un modèle comme celui-ci dans un collecteur commun:

template<typename T, typename A1, typename A2, typename A3> 
bool 
do_less_than(
     const typename T& t1, 
     const typename T& t2, 
     const typename A1 typename T::* a1, 
     const typename A2 typename T::* a2, 
     const typename A3 typename T::* a3) 
{ 
    if ((t1.*a1) < (t2.*a1)) return true; 
    if ((t1.*a1) != (t2.*a1)) return false; 
    if ((t1.*a2) < (t2.*a2)) return true; 
    if ((t1.*a2) != (t2.*a2)) return false; 
    return (t1.*a3) < (t2.*a3); 
} 

Ajouter d'autres modèles pour différents nombres d'arguments selon les besoins. Pour chaque moins de fonction, vous pouvez faire quelque chose comme ceci:

bool carLessThanComparator(const Car& car1, const Car& car2) 
{ 
    return do_less_than(car1, car2, &Car::make, &Car::model, &Car::year); 
} 
+0

Je vois rarement la syntaxe pointeur à membre en C++. Très intéressant. – jasonmray

+0

Et c'est un exemple de pourquoi –

+0

Le point principal est que la complexité est dans une bibliothèque et est créée une fois, tandis que le code commun devient plus simple. L'ordre de comparaison dans la fonction de comparaison réelle est évident et il n'y a pas de bogues de comparaison inversés. – janm

2

Personnellement, je le == passer outre, <,>, et tous les autres opérateurs nécessaires. Cela permettrait de nettoyer le code, pas dans la comparaison, mais quand vous avez besoin de faire la comparaison. Pour la comparaison elle-même, je l'écrirais de la même manière que Crashworks.

bool operator<(const Car &car1, const Car &car2) { 
    if(car1.make < car2.make) 
     return true; 
    if(car1.make != car2.make) 
     return false; 
    if(car1.model < car2.model) 
     return true; 
    if(car1.model != car2.model) 
     return false; 
    return car1.year < car2.year; 
} 
+1

Pourquoi ne pas simplement "retourner car1.year janm

+0

Je n'ai jamais trop regardé boost, mais je cherche simplement boost :: totally_ordered, ce serait utile. En ce qui concerne la dernière déclaration ... En fait, j'ai simplement copié le code de Crashwork quand j'ai vu que c'était le même style que j'utiliserais. Utiliser le retour comme vous l'avez dit serait mieux. – DeadHead

4

Personnellement, je vous suggère de ne pas utiliser le = ou == opérateurs comme nous semblent recommander ici - cela exige que les arguments/membres d'avoir les deux opérateurs moins égaux alors et juste pour faire moins de vérifier une classe les contenant - en utilisant seulement l'opérateur less than est suffisant et vous permettra d'économiser la redondance et les défauts potentiels dans le futur.

Je vous suggère d'écrire:

bool operator<(const Car &car1, const Car &car2) 
{ 
    if(car1.make < car2.make) 
     return true; 
    if(car2.make < car1.make) 
     return false; 

    if(car1.model < car2.model) 
     return true; 
    if(car2.model < car1.model) 
     return false; 

    return car1.year < car2.year; 
} 
+0

Bon point. Cela vous permet d'implémenter la comparaison inférieure à la structure plus grande avec seulement l'exigence que la comparaison est inférieure à la comparaison pour chaque membre. – jasonmray

1

Je me demandais la même chose que l'OP et sommes tombés sur cette question. Après avoir lu les réponses, j'ai été inspiré par janm et RnR pour écrire une fonction de modèle lexicographicalMemberCompare qui utilise seulement operator< sur les membres comparés. Il utilise également boost::tuple afin que vous puissiez spécifier autant de membres que vous le souhaitez. Le voici:

#include <iostream> 
#include <string> 
#include <boost/tuple/tuple.hpp> 

template <class T, class Cons> 
struct LessThan 
{ 
    static bool compare(const T& lhs, const T& rhs, const Cons& cons) 
    { 
     typedef LessThan<T, typename Cons::tail_type> NextLessThan; 
     typename Cons::head_type memberPtr = cons.get_head(); 
     return lhs.*memberPtr < rhs.*memberPtr ? 
      true : 
      (rhs.*memberPtr < lhs.*memberPtr ? 
       false : 
       NextLessThan::compare(lhs, rhs, cons.get_tail())); 
    } 
}; 

template <class T> 
struct LessThan<T, class boost::tuples::null_type> 
{ 
    static bool compare(const T& lhs, const T& rhs, 
         const boost::tuples::null_type& cons) 
    { 
     return false; 
    } 
}; 

template <class T, class Tuple> 
bool lexicographicalMemberCompare(const T& lhs, const T& rhs, 
            const Tuple& tuple) 
{ 
    return LessThan<T, typename Tuple::inherited>::compare(lhs, rhs, tuple); 
} 

struct Car 
{ 
    std::string make; 
    std::string model; 
    int year; 
}; 

bool carLessThanCompare(const Car& lhs, const Car& rhs) 
{ 
    return lexicographicalMemberCompare(lhs, rhs, 
     boost::tuples::make_tuple(&Car::make, &Car::model, &Car::year)); 
} 

int main() 
{ 
    Car car1 = {"Ford", "F150", 2009}; 
    Car car2 = {"Ford", "Escort", 2009}; 
    std::cout << carLessThanCompare(car1, car2) << std::endl; 
    std::cout << carLessThanCompare(car2, car1) << std::endl; 
    return 0; 
} 

Espérons que cela soit utile à quelqu'un.

+0

Vous avez un 'opérateur>'. De plus, les fonctions de comparaison ont tendance à être écrites en dehors de la classe. Sinon très chouette. – GManNickG

+0

Oups. Fixe 'operator>' et place 'operator <' en dehors de la classe. Je n'ai pas reçu le mémo sur la raison pour laquelle mettre les opérateurs de comparaison dans les classes est mauvais (quand le lhs est déjà la classe elle-même). –

+0

Oh, c'est vrai. Comparaison FONCTION. Désolé ... brainfart. :) –

3

Je sais qu'il est une vieille question, mais pour les futurs visiteurs: moderne C++ 11 solution est d'utiliser std::tie

struct Car{ 
    Manufacturer make; 
    ModelName model; 
    Year year; 
}; 

bool operator<(Car const& lhs, Car const& rhs) 
{ 
    return std::tie(lhs.make, lhs.model, lhs.year) < std::tie(rhs.make, rhs.model, rhs.year); 
} 

std::tie convertit le struct en std::tuple afin que les délégués de l'opérateur de comparaison ci-dessus à std::tuple::operator< . Ceci à son tour fait une comparaison lexicographique par rapport à l'ordre dans lequel les membres sont rassemblés en std::tie.

La comparaison lexicographique est court-circuitée de la même manière que dans les autres solutions à cette question. Mais il est même assez succinct pour définir à la volée dans une expression C++ lambda. Pour les classes avec des membres de données privés, il est préférable de définir la fonction friend dans la classe.