Простий і чистий спосіб порівняння трьох чисел


11

У мене є якийсь код, який має послідовність ifроботи, але просто відчуваю себе безладним. В основному, я хочу вибрати найбільше з трьох цілих чисел і встановити прапор статусу, щоб сказати, яке було вибрано. Мій поточний код виглядає приблизно так:

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;

Ця закономірність виникає кілька разів, і з довгими назвами змінних стає трохи важко візуально підтвердити, що кожен ifправильний. Я думаю, що може бути кращий і зрозуміліший спосіб зробити це; хтось може щось запропонувати?


Є кілька потенційних дублікатів, але вони не зовсім узгоджуються з цим питанням.

У запропонованому дублікаті: Підходи до перевірки кількох умов? всі запропоновані рішення здаються настільки ж незграбними, як оригінальний код, тому вони не забезпечують кращого рішення.

І ця посада Елегантні способи впоратися, якщо (якщо інше) інше стосується лише рівня гніздування та асиметрії, що тут не проблема.


3
Для мене єдина проблема - повторення коду. Що у вас тут є надзвичайно зрозумілим для читання, чому це змінювати? Просто розбийте його на функцію, яка приймає a, b, c і повертає статус. Якщо ви змусили себе почувати себе краще, опустіть на нього «рядок». Ніяких макросів, ніяких складностей, просто вилучення старої функції. Я буду вдячний за ваш чіткий код, якщо мені потрібно буде з ним пізніше працювати.
J Trana



Зауважте, що ваші #defines названі неправильно. Розглянемо a = 40, b = 30, c = 30. Результат - MOSTLY_A. Але більшість речей насправді не A, а B або C. Ви можете спробувати MORE_A (хоча все-таки неоднозначно - більше A, ніж B і більше A, ніж C, або більше A, ніж B і C разом?), Або A_LARGEST, MAX_IS_A, ...? (що також пропонує max (a, max (b, c)) як відповідь на питання).
тоні

Відповіді:


12

Факторизуйте логіку, повертайтеся рано

Як запропоновано в коментарях, досить було б просто зафіксувати свою логіку у функції та вийти з ранніх returnсимволів, щоб значно спростити речі. Крім того, ви можете поділити трохи функціональності, делегуючи тести в іншу функцію. Більш конкретно:

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

Це коротше моєї попередньої спроби:

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

Сказане вище є дещо докладнішим, але його легко читати IMHO і не перераховувати порівняння кілька разів.

Візуальне підтвердження

У своїй відповіді ви говорите:

моє питання здебільшого було візуальним підтвердженням того, що всі порівняння використовували однакові змінні

... також у своєму запитанні ви говорите:

Ця закономірність виникає кілька разів, і з довгими назвами змінних стає трохи важко візуально підтвердити, що кожен з них є правильним.

Я, можливо, не розумію, чого ви намагаєтеся досягти: чи хочете ви скопіювати та вставити шаблон там, де вам це потрібно? Завдяки такій функції, як описана вище, ви знімаєте шаблон один раз і перевіряєте один раз на всі, які використовуються всі порівняння a, bі cза необхідності. Тоді вам більше не потрібно хвилюватися під час виклику функції. Звичайно, можливо, на практиці ваша проблема трохи складніша за ту, яку ви описали: якщо так, то, будь-ласка, додайте деталі.


1
Я не розумію ваш коментар про DONT_KNOW , що робити , якщо c найменший, а a і b однакові? Алгоритм повертає, що b є найбільшим, а a - таким же, як b.
Пітер Б

@PieterB Мені було неприємно припускати, що це не має значення, повернемося ми MOSTLY_Aабо MOSTLY_Cу випадку, a == cі у випадку a > b. Це виправлено. Дякую.
coredump

@DocBrown Зрозуміло, більшість переваг отримують від поведінки на ранньому виході.
coredump

1
+1, тепер це справді вдосконалення щодо оригінального коду ОП.
Док Браун

9

TL: DR; Ваш код уже правильний і "чистий".

Я бачу дуже багато людей, які блукають у відповідь, але всі пропускають ліс через дерева. Зробимо повний інформатику та математичний аналіз, щоб повністю зрозуміти це питання.

Спочатку зазначимо, що у нас є 3 змінні, кожна з яких має 3 стани: <, = або>. Загальна кількість перестановок становить 3 ^ 3 = 27 станів, яким я призначу для кожного стану унікальне число, позначене P #. Це число № P - це фактична система числення .

Перерахування всіх перестановок, які ми маємо:

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

При огляді ми бачимо:

  • 3 стани, де A макс,
  • 3 стани, де B макс,
  • 3 стани, де C - max, і
  • 4 стану, де або A = B, або B = C.

Давайте напишемо програму (див. Виноску), щоб перерахувати всі ці перестановки зі значеннями для A, B і C. Стабільне сортування за 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

Якщо ви цікавились, як я знаю, які стани P # неможливі, тепер ви знаєте. :-)

Мінімальна кількість порівнянь для визначення замовлення:

Log2 (27) = Log (27) / Log (2) = ~ 4.75 = 5 порівнянь

тобто coredump дав правильні 5 мінімальних кількість порівнянь. Я б форматував його код так:

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

Для вашої проблеми нас не хвилює тестування на рівність, тому ми можемо опустити 2 тести.

Не має значення, наскільки чистий / поганий код, якщо він отримав неправильну відповідь, тому це хороший знак того, що ви керуєте всіма справами правильно!

Далі, що стосується простоти, люди продовжують намагатися «покращити» відповідь, де, на їхню думку, покращення означає «оптимізацію» кількості порівнянь, але це не зовсім те, що ви запитуєте. Ви плутали всіх, де ви запитували "я відчуваю, що може бути краще", але не визначили, що означає "краще". Менше порівнянь? Менший код? Оптимальні порівняння?

Тепер, оскільки ви запитуєте про читабельність коду (задана правильність), я б вніс лише одну зміну у ваш код для читабельності: вирівняйте перший тест з іншими.

        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

Я особисто написав би це так, але це може бути занадто неортодоксально для ваших стандартів кодування:

        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

Виноска. Ось код C ++ для створення перестановок:

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

Правки: На основі зворотного зв’язку переміщено TL: DR вгору, видалено несортовану таблицю, уточнено 27, очищений код, описано неможливі стани.


-1: чи не зменшення кількості рішень призведе до спрощення кодових шляхів та більш читаного коду? Ваш аргумент не зрозумілий: по-перше, ви говорите, що всі помиляються; тоді ви ставите не одну чи дві, а три таблиці; Я сподівався, що вони призведуть до більш простого способу обчислення результату, але замість цього ви підтвердили те, що всі інші вже знали (код ОП робить правильно). Звичайно, питання про читабельність, але читабельність не досягається лише зміною макета коду (ви визнаєте, що ваші зміни навряд чи відповідають існуючим стандартам коду). Є сенс спростити логіку при оптимізації для читабельності.
coredump

Більш конструктивно: я б запропонував спростити вашу відповідь, залишивши деякі деталі та подумавши про структуру вашої відповіді. Я вдячний, що вам знадобився час для написання та опублікування коду C ++, що генерує перестановки, але, можливо, ви могли б дати головний результат і лише одну таблицю: як це зараз, схоже, ви скинули всю свою роботу як є. Я майже не змогла помітити TL; річ DR (ви можете почати з цього). Сподіваюся, це допомагає.
coredump

2
Дякуємо за конструктивний зворотний зв'язок зворотного зв'язку. Я видалив середню несортовану таблицю, оскільки її легко перевірити.
Michaelangel007

2
Ісус Христос! Хто б сказав, що порівняння трьох чисел майже таке ж складне, як і ракетна наука?
Mandrill

@Mandrill Як інформатики, наша робота - глибоко розібратися в проблемі . Тільки перерахувавши всі 27 можливих перестановок для тристороннього порівняння, ми можемо перевірити, чи працює наше рішення у ВСІХ випадках. Останнє, що ми хочемо, як програмісти, - це приховані помилки та неврахувані кромки. Багатослівність - це ціна, яку ви платите за правильність.
Michaelangel007

5

@msw сказав вам використовувати масив замість a, b, c, а @Basile сказав вам переробляти логіку "max" на функцію. Поєднання цих двох ідей призводить до

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

тоді надайте функцію, яка обчислює максимальний індекс довільного масиву:

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

і називати це так

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

Загальна кількість LOC, схоже, збільшилась порівняно з початковою, але тепер у вас є основна логіка функції багаторазового використання, і якщо ви можете повторно використовувати функцію кілька разів, вона починає окупатися. Більше того, FindStrictMaxIndexфункція вже не переплітається з вашими "бізнес-вимогами" (розділення проблем), тому ризик, який вам доведеться змінити пізніше, значно нижчий, ніж у початковій версії (принцип відкритого закриття). Наприклад, цю функцію не доведеться змінювати, навіть якщо кількість аргументів змінюється або потрібно використовувати інші повернені значення, ніж MOSTLY_ABC, або ви обробляєте інші змінні, ніж a, b, c. Крім того, використання масиву замість 3 різних значень a, b, c може спростити ваш код і в інших місцях.

Звичайно, якщо у всій вашій програмі є лише одне або два місця для виклику цієї функції, і у вас немає додаткових додатків для зберігання значень у масиві, я, ймовірно, залишу початковий код таким, який він є (або використовувати @ поліпшення coredump).


Мені це подобається - кишки FindStrictMaxIndex()можуть бути не надто чистими, але з точки зору того, хто телефонує, цілком очевидно, чого намагаються досягти.
Кен ІН

Або замість того, щоб утримувати два масиви, утримуйте один масив пар ключ-значення: {MOSTLY_A, countAs ()}, візьміть перший елемент, упорядкований за значенням, і прочитайте ключ.
Джулія Хейвард

@JuliaHayward: Основною причиною того, що я не пропонував таке рішення, був тег "C" питання - у C потрібен ще якийсь код коробки, щоб розібратися з парами ключових значень та створити функцію, набрану в термінах KVP ймовірно, буде не таким багаторазовим використання в різних контекстах, ніж проста intфункція набору тексту. Але я погоджуюся з вашим коментарем, якщо хтось використовує іншу мову, як Python або Perl.
Doc Brown

1
@ gnasher729: це залежить від кількості "дублікатів" в початковому коді, наскільки вони схожі насправді та як часто FindStrictMaxIndexфункцію можна використовувати повторно. За один чи два рази повторного використання це, звичайно, не окупиться, але про це я вже писав у своїй відповіді. Зауважте також інші переваги, про які я згадував вище, стосовно майбутніх змін.
Док Браун

1
... і зауважте, що оригінальні 8 рядків можна замінити простим однокласником return result[FindStrictMaxIndex(val,3)]; у точці коду, де були розміщені початкові 8 рядків . Інші частини, особливо FindStrictMaxIndexсама, повністю відокремлена від "ділової логіки", яка виводить їх з фокусу змін вимог бізнесу.
Док Браун

-1

Ймовірно, ви повинні використовувати макрос або функцію, що MAX дає максимум два числа.

Тоді ви просто хочете:

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

Ви, можливо, визначилися

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

але будьте обережні - особливо щодо побічних ефектів - при використанні макросів (оскільки MAX(i++,j--) ведуть себе дивно)

Тож краще визначте функцію

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

і використовувати його (або принаймні #define MAX(X,Y) max2ints((X),(Y))....)

Якщо вам потрібно зрозуміти походження MAX, у вас може бути довгий макрос, #define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) який є довгим do{... }while(0) макросом, можливо

#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)

Тоді ви можете викликати COMPUTE_MAX_WITH_CAUSE(status,res,a,b,c) в декількох місцях. Це трохи некрасиво. Я визначив локальні змінні x, y, z знизити шкідливі побічні ефекти ....


2
Перевстановлення загальної логіки у функцію - це правильний підхід, але я б насправді уникав двох речей тут: 1. Я б не «вигадував» нових вимог (ОП не просив обчислити максимум). І друге: навіть якщо отриманий код може стати DRY, це дуже дискусійно, якщо це виправдовує складний макрос.
Doc Brown

1
Макроси повинні бути інструментом останньої інстанції. Однозначно поза межами цієї проблеми.
Кевін Клайн

-1

У мене було більше думок з цього приводу, тому оскільки мій випуск був здебільшого візуальним підтвердженням того, що всі порівняння використовували однакові змінні, я думаю, це може бути корисним підходом:

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

Що кожен макрос приймає a, bі cв тому самому порядку підтверджується легко, а ім’я макросу врятує мене, що я повинен розібратися з тим, що роблять усі порівняння та AND.


1
(1) чому допоміжні макроси замість функцій? (2) навіщо вам тут потрібне візуальне підтвердження? це справді ваша основна проблема чи необхідність візуального підтвердження є наслідком дублювання коду? Найкращий варіант - розподілити код на єдину просту функцію, яку ви перевіряєте раз на раз .
coredump
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.