web-dev-qa-db-fra.com

Une façon simple et propre de comparer trois nombres

J'ai du code qui a une séquence de if qui fonctionne, mais je me sens juste en désordre. Fondamentalement, je veux choisir le plus grand des trois entiers et définir un indicateur d'état pour indiquer celui qui a été choisi. Mon code actuel ressemble à ceci:

a = countAs();
b = countBs();
c = countCs();

if (a > b && a > c)
    status = MOSTLY_A;
else if (b > a && b > c)
    status = MOSTLY_B;
else if (c > a && c > b)
    status = MOSTLY_C;
else
    status = DONT_KNOW;

Ce modèle se produit plusieurs fois et avec des noms de variable longs, il devient un peu difficile de confirmer visuellement que chaque if est correct. Je pense qu'il pourrait y avoir une façon meilleure et plus claire de le faire; quelqu'un peut-il suggérer quelque chose?


Il existe des doublons potentiels, mais ils ne correspondent pas tout à fait à cette question.

Dans le doublon suggéré: Approches pour vérifier plusieurs conditions? toutes les solutions suggérées semblent aussi maladroites que le code original, donc elles ne fournissent pas une meilleure solution.

Et ce post Des manières élégantes de gérer si (sinon) ne traite que des niveaux d'imbrication et de l'asymétrie, ce qui n'est pas le problème ici.

11
Ken Y-N

Factoriser la logique, revenir tôt

Comme suggéré dans les commentaires, il suffirait de simplement encapsuler votre logique dans une fonction et de quitter tôt avec return pour simplifier beaucoup les choses. De plus, vous pouvez factoriser un peu de fonctionnalité en déléguant des tests à une autre fonction. Plus concrètement:

bool mostly(max,u,v) {
   return max > u && max > v;
}

status_t strictly_max_3(a,b,c)
{
  if mostly(a,b,c) return MOSTLY_A;
  if mostly(b,a,c) return MOSTLY_B;
  if mostly(c,a,b) return MOSTLY_C;
  return DONT_KNOW;
}

C'est plus court que ma précédente tentative:

status_t index_of_max_3(a,b,c)
{
  if (a > b) {
    if (a == c)
      return DONT_KNOW;
    if (a > c)
      return MOSTLY_A;
    else
      return MOSTLY_C;
  } else {
    if (a == b)
      return DONT_KNOW;
    if (b > c)
      return MOSTLY_B;
    else
      return MOSTLY_C;
  }
}

Ce qui précède est un peu plus détaillé, mais il est facile à lire à mon humble avis et ne recalcule pas les comparaisons plusieurs fois.

Confirmation visuelle

Dans votre réponse vous dites:

mon problème était surtout la confirmation visuelle que toutes les comparaisons utilisaient les mêmes variables

... aussi, dans votre question, vous dites:

Ce modèle se produit plusieurs fois, et avec de longs noms de variables, il devient un peu difficile de confirmer visuellement que chaque if est correct.

Je ne comprends peut-être pas ce que vous essayez de réaliser: voulez-vous copier-coller le modèle partout où vous en avez besoin? Avec une fonction comme celle ci-dessus, vous capturez le motif une fois et vous vérifiez une fois pour toutes que toutes les comparaisons utilisent a, b et c comme requis. Ensuite, vous n'avez plus à vous inquiéter lorsque vous appelez la fonction. Bien sûr, dans la pratique, votre problème est peut-être un peu plus complexe que celui que vous avez décrit: si c'est le cas, veuillez ajouter des détails si possible.

12
coredump

TL: DR; Votre code est déjà correct et "propre".

Je vois beaucoup de gens se débattre autour de la réponse, mais tout le monde manque la forêt à travers les arbres. Faisons toute l'informatique et l'analyse mathématique pour bien comprendre cette question.

Tout d'abord, nous notons que nous avons 3 variables, chacune avec 3 états: <, = ou>. Le nombre total de permutations est de 3 ^ 3 = 27 états, auxquels j'attribuerai un numéro unique, noté P #, pour chaque état. Ce nombre P # est un système numérique factoriel .

Enumérant toutes les permutations que nous avons:

a ? b | a ? c | b ? c |P#| State
------+-------+-------+--+------------
a < b | a < c | b < c | 0| C
a = b | a < c | b < c | 1| C
a > b | a < c | b < c | 2| C
a < b | a = c | b < c | 3| impossible a<b b<a
a = b | a = c | b < c | 4| impossible a<a
a > b | a = c | b < c | 5| A=C > B
a < b | a > c | b < c | 6| impossible a<c a>c
a = b | a > c | b < c | 7| impossible a<c a>c
a > b | a > c | b < c | 8| A
a < b | a < c | b = c | 9| B=C > A
a = b | a < c | b = c |10| impossible a<a
a > b | a < c | b = c |11| impossible a<c a>c
a < b | a = c | b = c |12| impossible a<a
a = b | a = c | b = c |13| A=B=C
a > b | a = c | b = c |14| impossible a>a
a < b | a > c | b = c |15| impossible a<c a>c
a = b | a > c | b = c |16| impossible a>a
a > b | a > c | b = c |17| A
a < b | a < c | b > c |18| B
a = b | a < c | b > c |19| impossible b<c b>c
a > b | a < c | b > c |20| impossible a<c a>c
a < b | a = c | b > c |21| B
a = b | a = c | b > c |22| impossible a>a
a > b | a = c | b > c |23| impossible c>b b>c
a < b | a > c | b > c |24| B
a = b | a > c | b > c |25| A=B > C
a > b | a > c | b > c |26| A

Par inspection, nous voyons que nous avons:

  • 3 états où A est le maximum,
  • 3 états où B est le maximum,
  • 3 états où C est le maximum, et
  • 4 états où A = B ou B = C.

Écrivons un programme (voir la note de bas de page) pour énumérer toutes ces permutations avec des valeurs pour A, B et C. Tri stable par P #:

a ?? b | a ?? c | b ?? c |P#| State
1 <  2 | 1 <  3 | 2 <  3 | 0| C
1 == 1 | 1 <  2 | 1 <  2 | 1| C
1 == 1 | 1 <  3 | 1 <  3 | 1| C
2 == 2 | 2 <  3 | 2 <  3 | 1| C
2  > 1 | 2 <  3 | 1 <  3 | 2| C
2  > 1 | 2 == 2 | 1 <  2 | 5| ??
3  > 1 | 3 == 3 | 1 <  3 | 5| ??
3  > 2 | 3 == 3 | 2 <  3 | 5| ??
3  > 1 | 3  > 2 | 1 <  2 | 8| A
1 <  2 | 1 <  2 | 2 == 2 | 9| ??
1 <  3 | 1 <  3 | 3 == 3 | 9| ??
2 <  3 | 2 <  3 | 3 == 3 | 9| ??
1 == 1 | 1 == 1 | 1 == 1 |13| ??
2 == 2 | 2 == 2 | 2 == 2 |13| ??
3 == 3 | 3 == 3 | 3 == 3 |13| ??
2  > 1 | 2  > 1 | 1 == 1 |17| A
3  > 1 | 3  > 1 | 1 == 1 |17| A
3  > 2 | 3  > 2 | 2 == 2 |17| A
1 <  3 | 1 <  2 | 3  > 2 |18| B
1 <  2 | 1 == 1 | 2  > 1 |21| B
1 <  3 | 1 == 1 | 3  > 1 |21| B
2 <  3 | 2 == 2 | 3  > 2 |21| B
2 <  3 | 2  > 1 | 3  > 1 |24| B
2 == 2 | 2  > 1 | 2  > 1 |25| ??
3 == 3 | 3  > 1 | 3  > 1 |25| ??
3 == 3 | 3  > 2 | 3  > 2 |25| ??
3  > 2 | 3  > 1 | 2  > 1 |26| A

Au cas où vous vous demandiez comment je savais quels états P # étaient impossibles, maintenant vous savez. :-)

Le nombre minimum de comparaisons pour déterminer la commande est:

Log2 (27) = Log (27)/Log (2) = ~ 4,75 = 5 comparaisons

c.-à-d. coredump a donné le nombre minimal correct de 5 comparaisons. Je formaterais son code comme:

status_t index_of_max_3(a,b,c)
{
    if (a > b) {
        if (a == c) return DONT_KNOW; // max a or c
        if (a >  c) return MOSTLY_A ;
        else        return MOSTLY_C ;
    } else {
        if (a == b) return DONT_KNOW; // max a or b
        if (b >  c) return MOSTLY_B ;
        else        return MOSTLY_C ;
    }
}

Pour votre problème, nous ne nous soucions pas de tester l'égalité, nous pouvons donc omettre 2 tests.

Peu importe à quel point le code est propre/mauvais s'il obtient une mauvaise réponse, c'est donc un bon signe que vous gérez tous les cas correctement!

Ensuite, en ce qui concerne la simplicité, les gens continuent d'essayer "d'améliorer" la réponse, où ils pensent qu'améliorer signifie "optimiser" le nombre de comparaisons, mais ce n'est pas strictement ce que vous demandez. Vous avez confondu tout le monde lorsque vous avez demandé "Je pense qu'il pourrait y avoir un meilleur", mais n'avez pas défini ce que "mieux" signifie. Moins de comparaisons? Moins de code? Des comparaisons optimales?

Maintenant que vous demandez la lisibilité du code (étant donné l'exactitude), je ne ferais qu'une seule modification à votre code pour la lisibilité: Alignez le premier test avec les autres.

        if      (a > b && a > c)
            status = MOSTLY_A;
        else if (b > a && b > c)
            status = MOSTLY_B;
        else if (c > a && c > b)
            status = MOSTLY_C;
        else
            status = DONT_KNOW; // a=b or b=c, we don't care

Personnellement, je l'écrirais de la manière suivante, mais cela peut être trop peu orthodoxe pour vos normes de codage:

        if      (a > b && a > c) status = MOSTLY_A ;
        else if (b > a && b > c) status = MOSTLY_B ;
        else if (c > a && c > b) status = MOSTLY_C ;
        else /*  a==b  || b ==c*/status = DONT_KNOW; // a=b or b=c, we don't care

Note de bas de page: Voici le code C++ pour générer les permutations:

#include <stdio.h>

char txt[]  = "< == > ";
enum cmp      { LESS, EQUAL, GREATER };
int  val[3] = { 1, 2, 3 };

enum state    { DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C };
char descr[]= "??A B C ";

cmp Compare( int x, int y ) {
    if( x < y ) return LESS;
    if( x > y ) return GREATER;
    /*  x==y */ return EQUAL;
}

int main() {
    int i, j, k;
    int a, b, c;

    printf( "a ?? b | a ?? c | b ?? c |P#| State\n" );
    for( i = 0; i < 3; i++ ) {
        a = val[ i ];
        for( j = 0; j < 3; j++ ) {
            b = val[ j ];
            for( k = 0; k < 3; k++ ) {
                c = val[ k ];

                int cmpAB = Compare( a, b );
                int cmpAC = Compare( a, c );
                int cmpBC = Compare( b, c );
                int n     = (cmpBC * 9) + (cmpAC * 3) + cmpAB; // Reconstruct unique P#

                printf( "%d %c%c %d | %d %c%c %d | %d %c%c %d |%2d| "
                    , a, txt[cmpAB*2+0], txt[cmpAB*2+1], b
                    , a, txt[cmpAC*2+0], txt[cmpAC*2+1], c
                    , b, txt[cmpBC*2+0], txt[cmpBC*2+1], c
                    , n
                );

                int status;
                if      (a > b && a > c) status = MOSTLY_A;
                else if (b > a && b > c) status = MOSTLY_B;
                else if (c > a && c > b) status = MOSTLY_C;
                else /*  a ==b || b== c*/status = DONT_KNOW; // a=b, or b=c

                printf( "%c%c\n", descr[status*2+0], descr[status*2+1] );
            }
        }
    }
    return 0;
}

Modifications: sur la base des commentaires, déplacé TL: DR vers le haut, supprimé la table non triée, clarifié 27, nettoyé le code, décrit les états impossibles.

9
Michaelangel007

@msw vous a dit d'utiliser un tableau au lieu de a, b, c et @Basile vous a dit de refactoriser la logique "max" en fonction. La combinaison de ces deux idées conduit à

val[0] = countAs();    // in the real code, one should probably define 
val[1] = countBs();    // some enum for the indexes 0,1,2 here
val[2] = countCs();

 int result[]={DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C};

fournir ensuite une fonction qui calcule l'index max d'un tableau arbitraire:

// returns the index of the strict maximum, and -1 when the maximum is not strict
int FindStrictMaxIndex(int *values,int arraysize)
{
    int maxVal=INT_MIN;
    int maxIndex=-1;
    for(int i=0;i<arraysize;++i)
    {
       if(values[i]>maxVal)
       {
         maxVal=values[i];
         maxIndex=i;
       }
       else if (values[i]==maxVal)
       {
         maxIndex=-1;
       }
    }
    return maxIndex;
}

et l'appelle comme

 return result[FindStrictMaxIndex(val,3)+1];

Le nombre total de LOC semble avoir augmenté par rapport à celui d'origine, mais maintenant vous avez la logique principale dans une fonction réutilisable, et si vous pouvez réutiliser la fonction plusieurs fois, cela commence à porter ses fruits. De plus, la fonction FindStrictMaxIndex n'est plus liée à vos "exigences métiers" (séparation des soucis), donc le risque que vous aurez à la modifier ultérieurement est beaucoup plus faible que dans votre version d'origine (principe ouvert-fermé) ). Par exemple, cette fonction ne devra pas être modifiée même si le nombre d'arguments change, ou si vous devez utiliser d'autres valeurs de retour que MOSTLY_ABC, ou si vous traitez d'autres variables que a, b, c. De plus, l'utilisation d'un tableau au lieu de 3 valeurs différentes a, b, c pourrait également simplifier votre code à d'autres endroits.

Bien sûr, si dans tout votre programme il n'y a qu'un ou deux endroits pour appeler cette fonction, et que vous n'avez pas d'autres applications pour conserver les valeurs dans un tableau, alors je laisserais probablement le code d'origine tel quel (ou utilisez @ amélioration de coredump).

5
Doc Brown