Modo semplice e pulito di confrontare tre numeri


11

Ho del codice che ha una sequenza di ifs che funzionano, ma mi sento confuso. Fondamentalmente, voglio scegliere il più grande dei tre numeri interi e impostare un flag di stato per dire quale è stato scelto. Il mio codice attuale è simile al seguente:

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;

Questo schema si verifica alcune volte e con nomi di variabili lunghe diventa un po 'difficile confermare visivamente che ciascuno ifsia corretto. Sento che potrebbe esserci un modo migliore e più chiaro per farlo; qualcuno può suggerire qualcosa?


Ci sono alcuni potenziali duplicati, ma non si allineano perfettamente a questa domanda.

Nel duplicato suggerito: approcci al controllo di più condizioni? tutte le soluzioni suggerite sembrano ugualmente goffe come il codice originale, quindi non forniscono una soluzione migliore.

E questo post Modi eleganti per gestire se (se altro) altro si occupa solo di livelli di nidificazione e asimmetria, che non è il problema qui.


3
Per me l'unico problema è la ripetizione del codice. Quello che hai qui è estremamente chiaro da leggere, perché cambiarlo? Basta dividerlo in una funzione che accetta a, b, ce restituisce lo stato. Se ti fa sentire meglio, lascia un "inline" su di esso. Nessuna macro, nessuna complessità, solo una buona estrazione di vecchie funzioni. Ti ringrazierò per il tuo codice chiaro se dovrò lavorarci più tardi.
J Trana,



Nota che i tuoi #define sono nominati male. Considera a = 40, b = 30, c = 30. Il risultato è MOSTLY_A. Ma la maggior parte delle cose non sono in realtà A, ma B o C. Potresti provare MORE_A (anche se ancora ambiguo - più A di B e più A di C, o più A di B e C combinati?) O A_LARGEST, MAX_IS_A, ...? (che suggerisce anche max (a, max (b, c)) come risposta alla domanda).
tony,

Risposte:


12

Fattorizza la logica, ritorna presto

Come suggerito nei commenti, sarebbe sufficiente semplicemente avvolgere la tua logica in una funzione e uscire presto con quella returnper semplificare molto le cose. Inoltre, potresti fattorizzare un po 'di funzionalità delegando i test ad un'altra funzione. Più concretamente:

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

Questo è più breve del mio precedente tentativo:

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

Quanto sopra è un po 'più dettagliato, ma è facile da leggere IMHO e non ricalcola i confronti più volte.

Conferma visiva

Nella tua risposta dici:

il mio problema era principalmente la conferma visiva che tutti i confronti utilizzavano le stesse variabili

... inoltre, nella tua domanda, dici:

Questo schema si verifica alcune volte e con nomi di variabili lunghe diventa un po 'difficile confermare visivamente che ciascuno se è corretto.

Potrei non capire cosa stai cercando di ottenere: vuoi copiare e incollare lo schema ovunque tu ne abbia bisogno? Con una funzione come quella sopra, catturi il modello una volta e controlli una volta per tutto quello che usano tutti i confronti a, be ccome richiesto. Quindi, non è più necessario preoccuparsi quando si chiama la funzione. Certo, forse in pratica il tuo problema è un po 'più complesso di quello che hai descritto: in tal caso, aggiungi alcuni dettagli se possibile.


1
Non capisco il tuo commento su DONT_KNOW , cosa succede se c è il più piccolo e aeb sono uguali? L'algoritmo restituisce che b è il più grande, mentre a è uguale a b.
Pieter B,

@PieterB Ero preoccupato supponendo che non sarebbe importato se fossimo tornati MOSTLY_Ao MOSTLY_Cnel caso a == ce a > b. Questo è stato risolto. Grazie.
coredump,

@DocBrown Concesso, la maggior parte dei vantaggi deriva dal comportamento di uscita anticipata.
coredump,

1
+1, ora è davvero un miglioramento rispetto al codice originale dei PO.
Doc Brown,

9

TL: DR; Il tuo codice è già corretto e "pulito".

Vedo un sacco di persone preoccuparsi della risposta, ma a tutti manca la foresta tra gli alberi. Facciamo l'informatica completa e l'analisi matematica per comprendere completamente questa domanda.

Innanzitutto, notiamo che abbiamo 3 variabili, ognuna con 3 stati: <, =, o>. Il numero totale di permutazioni è 3 ^ 3 = 27 stati, che assegnerò un numero univoco, indicato con P #, per ogni stato. Questo numero P # è un sistema numerico fattoriale .

Enumerando tutte le permutazioni che abbiamo:

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

Dall'ispezione vediamo che abbiamo:

  • 3 indica dove A è il massimo,
  • 3 indica dove B è il massimo,
  • 3 indica dove C è il massimo e
  • 4 stati in cui A = B o B = C.

Scriviamo un programma (vedi la nota a piè di pagina) per elencare tutte queste permutazioni con valori per A, B e C. Ordinamento stabile per 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

Nel caso ti stavi chiedendo come sapessi quali stati P # erano impossibili, ora lo sai. :-)

Il numero minimo di confronti per determinare l'ordine è:

Log2 (27) = Log (27) / Log (2) = ~ 4.75 = 5 confronti

cioè coredump ha dato il numero minimo corretto 5 di confronti. Vorrei formattare il suo codice come:

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

Per il tuo problema non ci interessa testare l'uguaglianza, quindi possiamo omettere 2 test.

Non importa quanto sia pulito / cattivo il codice se ottiene la risposta sbagliata, quindi questo è un buon segno che stai gestendo tutti i casi correttamente!

Successivamente, per quanto riguarda la semplicità, le persone continuano a cercare di "migliorare" la risposta, dove pensano che migliorare significhi "ottimizzare" il numero di confronti, ma non è esattamente quello che stai chiedendo. Hai confuso tutti quelli in cui hai chiesto "Sento che potrebbe esserci un migliore" ma non hai definito cosa significa "meglio". Meno confronti? Meno codice? Confronti ottimali?

Ora, poiché stai chiedendo informazioni sulla leggibilità del codice (data la correttezza), apporterei una sola modifica al tuo codice per leggibilità: Allinea il primo test con gli altri.

        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

Personalmente lo scriverei nel modo seguente ma questo potrebbe essere troppo poco ortodosso per i tuoi standard di codifica:

        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

Nota a piè di pagina: ecco il codice C ++ per generare le permutazioni:

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

Modifiche: in base al feedback, spostato TL: DR in alto, tabella non ordinata rimossa, chiarito 27, codice ripulito, stati impossibili descritti.


-1: la riduzione del numero di decisioni non porterebbe a percorsi di codice più semplici e codice più leggibile? La tua argomentazione non è chiara: in primo luogo, dici che tutti hanno torto; quindi non metti uno o due ma tre tavoli; Speravo che avrebbero portato a un modo più semplice per calcolare il risultato, ma invece hai confermato ciò che tutti già sapevano (il codice OP fa la cosa giusta). Certo, la domanda riguarda la leggibilità, ma la leggibilità non si ottiene solo modificando il layout del codice (ammetti che le tue modifiche difficilmente si adattano agli standard di codice esistenti). Ha senso semplificare la logica quando si ottimizza per la leggibilità.
coredump,

Più costruttivo: suggerirei di semplificare la tua risposta tralasciando alcuni dettagli e pensando alla struttura della tua risposta. Apprezzo che tu abbia impiegato del tempo per scrivere e pubblicare il codice C ++ generando permutazioni, ma forse potresti dare il risultato principale e solo una tabella: com'è ora, sembra che tu abbia scaricato tutto il tuo lavoro così com'è. Ho quasi fallito nell'individuare la cosa TL; DR (potresti iniziare con quella). Spero che sia d'aiuto.
coredump,

2
Grazie per il feedback costruttivo coredump. Ho rimosso la tabella centrale non ordinata poiché è facilmente verificabile.
Michaelangel007,

2
Gesù Cristo! Chi direbbe che confrontare tre numeri è quasi complesso quanto la scienza missilistica?
Mandrill,

@Mandrill Come scienziati informatici è nostro compito comprendere a fondo un problema . Solo elencando tutte le 27 possibili permutazioni per un confronto a 3 vie possiamo verificare che la nostra soluzione funzioni in TUTTI i casi. L'ultima cosa che vogliamo come programmatori sono bug nascosti e casi limite non contabilizzati. La verbosità è il prezzo che si paga per la correttezza.
Michaelangel007,

5

@msw ti ha detto di usare un array invece di a, b, c, e @Basile ti ha detto di refactorizzare la logica "max" in una funzione. La combinazione di queste due idee porta a

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

quindi fornire una funzione che calcola l'indice massimo di un array arbitrario:

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

e chiamalo come

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

Il numero totale di LOC sembra essere aumentato rispetto a quello originale, ma ora hai la logica principale in una funzione riutilizzabile e se puoi riutilizzare la funzione più volte, inizia a pagare. Inoltre, la FindStrictMaxIndexfunzione non è più intrecciata con i tuoi "requisiti aziendali" (separazione delle preoccupazioni), quindi il rischio che dovrai modificarlo in seguito è molto più basso rispetto alla versione originale (principio aperto-chiuso). Ad esempio, tale funzione non dovrà essere modificata anche se il numero di argomenti cambia o è necessario utilizzare valori di ritorno diversi da MOSTLY_ABC o se si stanno elaborando variabili diverse da a, b, c. Inoltre, l'uso di un array invece di 3 valori diversi a, b, c potrebbe semplificare il codice anche in altri luoghi.

Naturalmente, se nel tuo intero programma ci sono solo uno o due posti per chiamare questa funzione e non hai altre applicazioni per contenere i valori in un array, probabilmente lascerei il codice originale così com'è (o uso @ miglioramento di coredump).


Mi piace - l'intestino FindStrictMaxIndex()potrebbe non essere troppo pulito, ma dal punto di vista del chiamante è ragionevolmente ovvio cosa sta cercando di ottenere.
Ken YN,

O invece di contenere due array, tieni un array di coppie chiave-valore: {MOSTLY_A, countAs ()}, prendi il primo elemento ordinato per valore e leggi la chiave.
Julia Hayward,

@JuliaHayward: il motivo principale per cui non ho suggerito una soluzione del genere era il tag "C" della domanda - in C, ci sarà bisogno di un po 'più di codice boilerplate per gestire le coppie chiave-valore e creare una funzione digitata in termini di KVP probabilmente non sarà riutilizzabile in contesti diversi rispetto a una semplice intfunzione tipizzata. Ma sono d'accordo con il tuo commento se uno usa un linguaggio diverso come Python o Perl.
Doc Brown,

1
@ gnasher729: dipende da quanti "duplicati" sono nel codice originale, da quanto sono realmente simili e da quanto spesso la FindStrictMaxIndexfunzione può essere riutilizzata. Per una o due volte di riutilizzo, questo non ripagherà, ovviamente, ma è quello che ho già scritto nella mia risposta. Nota anche gli altri vantaggi che ho menzionato sopra riguardo a cambiamenti futuri.
Doc Brown,

1
... e nota che le 8 linee originali possono essere sostituite da una semplice return result[FindStrictMaxIndex(val,3)]; riga nel punto in cui sono state posizionate le 8 linee originali . Le altre parti, in particolare FindStrictMaxIndexse stessa, sono completamente separate dalla "logica aziendale", che le allontana dal centro delle mutevoli esigenze aziendali.
Doc Brown,

-1

Probabilmente dovresti usare una macro o una funzione che MAX dia il massimo di due numeri.

Quindi vuoi solo:

 status = MAX(a,MAX(b,c));

Potresti aver definito

 #define MAX(X,Y) (((X)>(Y))?(X):(Y))

ma fai attenzione, in particolare sugli effetti collaterali, quando usi le macro (dal momento che MAX(i++,j--) si comporterebbero in modo strano)

Quindi meglio definire una funzione

 static inline int max2ints(int x, int y) {
    return (x>y)?x:y;
 }

e usalo (o almeno #define MAX(X,Y) max2ints((X),(Y))....)

Se hai bisogno di capire l'origine del MAX potresti avere una macro lunga come quella #define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) che è una lunga do{... }while(0) macro, forse

#define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) do { \
  int x= (X), y= (Y), z=(Z); \
  if (x > y && y > z) \
    { Status = MOSTLY_FIRST; Result = x; } \
  else if (y > x && y > z) \
    { Status = MOSTLY_SECOND; Result = y; } \
  else if (z > x && z > y) \
    { Status = MOSTLY_THIRD; Result = z; } \
  /* etc */ \
  else { Status = UNKNOWN; Result = ... } \
} while(0)

Quindi potresti invocare COMPUTE_MAX_WITH_CAUSE(status,res,a,b,c) in diversi posti. È un po 'brutto. Ho definito le variabili locali x, y, z di abbassare effetti collaterali male ....


2
Rifattorizzare la logica comune in una funzione è l'approccio giusto, ma in questo caso eviterei due cose: 1. Non "inventerei" nuovi requisiti (l'OP non ha chiesto di calcolare il massimo). E secondo: anche se il codice risultante può diventare più ASCIUTTO, è molto discutibile se ciò giustifica una macro complicata.
Doc Brown,

1
Le macro dovrebbero essere uno strumento di ultima istanza. Decisamente fuori limite per questo problema.
Kevin Cline il

-1

Ho pensato più a questo, quindi dato che il mio problema era principalmente la conferma visiva che tutti i confronti utilizzavano le stesse variabili, penso che questo potrebbe essere un approccio utile:

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

if (FIRST_IS_LARGEST(a, b, c))
    status = MOSTLY_A;
else if (SECOND_IS_LARGEST(a, b, c))
    status = MOSTLY_B;
else if (THIRD_IS_LARGEST(a, b, c))
    status = MOSTLY_C;
else
    status = DONT_KNOW; /* NO_SINGLE_LARGEST is a better name? */

Che ogni macro prende a, be cnello stesso ordine è facile da confermare, e il nome della macro mi evita di dover capire cosa stanno facendo tutti i confronti e gli AND.


1
(1) perché le macro ausiliarie anziché le funzioni? (2) perché hai bisogno di una conferma visiva qui? è davvero il tuo problema principale o la necessità di una conferma visiva è una conseguenza della duplicazione del codice? La tua migliore opzione è quella di scomporre il tuo codice in un'unica, semplice funzione che controlli una volta per tutte .
coredump,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.