세 개의 숫자를 비교하는 간단하고 깨끗한 방법


11

if작동 하는 일련의 코드가 있지만 지저분 해집니다. 기본적으로 3 개의 정수 중 가장 큰 정수를 선택하고 선택된 플래그를 나타내는 상태 플래그를 설정하고 싶습니다. 내 현재 코드는 다음과 같습니다

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이 올바른지 시각적으로 확인하기가 약간 어려워집니다 . 더 좋은 방법이있을 것 같습니다. 누구든지 제안 할 수 있습니까?


잠재적 중복이 있지만이 질문과는 일치하지 않습니다.

제안 된 복제본 : 여러 조건을 확인하는 방법? 제안 된 모든 솔루션은 원래 코드와 똑같이 서투른 것처럼 보이므로 더 나은 솔루션을 제공하지 않습니다.

그리고이 게시물 if (그렇다면) 그렇지 않으면 중첩 수준과 비대칭 만 다루는 우아한 방법 은 문제가되지 않습니다.


3
나에게 유일한 문제는 코드 반복입니다. 여기에있는 내용은 읽기가 매우 명확합니다. 왜 변경해야합니까? a, b, c를 취하고 상태를 반환하는 함수로 분리하십시오. 기분이 좋아지면 "인라인"을 떨어 뜨립니다. 매크로도, 복잡도도없고, 오래된 함수 추출 만 가능합니다. 나중에 작업해야 할 경우 명확한 코드에 감사드립니다.
J Trana



#defines의 이름이 잘못되었습니다. a = 40, b = 30, c = 30을 고려하십시오. 결과는 MOSTLY_A입니다. 그러나 대부분 의 것은 실제로 A가 아니라 B ​​또는 C입니다. MORE_A를 시도하고 싶을 수도 있습니다. MAX_IS_A, ...? (또한 질문에 대한 답변으로 max (a, max (b, c))를 제안합니다).
tony

답변:


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, bc필요에 따라. 그러면 함수를 호출 할 때 더 이상 걱정할 필요가 없습니다. 물론, 실제로는 문제가 설명했던 것보다 약간 더 복잡 할 수 있습니다. 그렇다면 가능하면 세부 정보를 추가하십시오.


1
DONT_KNOW에 대한 귀하의 의견을 이해하지 못합니다. c가 가장 작고 a와 b가 같은 경우 어떻게해야합니까? 알고리즘은 b가 가장 크지 만 a는 b와 동일하다는 것을 반환합니다.
Pieter B

@PieterB 나는 우리가 하나 반환하지 않을 문제가있는 경우 가정 wronlgy이었다 MOSTLY_A또는 MOSTLY_C경우에 a == ca > b. 이것은 고정되어 있습니다. 감사.
coredump 2018 년

@DocBrown 물론 대부분의 혜택은 조기 퇴장 행동에서 비롯됩니다.
코어 덤프

1
+1, 이제 OPs 원본 코드보다 개선되었습니다.
Doc Brown

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

검사를 통해 우리는 다음을 볼 수 있습니다.

  • A가 최대 인 3 가지 상태
  • B가 최대 인 3 가지 상태,
  • C가 최대 인 3 가지 상태
  • 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 비교

즉, 코어 덤프는 정확한 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을 맨 위로 이동하고 정렬되지 않은 테이블을 제거하고 명확하게 정리하고 코드를 정리하고 불가능한 상태를 설명했습니다.


-1 : 의사 결정 횟수를 줄이면 더 간단한 코드 경로와 더 읽기 쉬운 코드로 이어지지 않습니까? 당신의 주장은 명확하지 않습니다 : 첫째, 당신은 모두가 틀렸다고 말합니다. 그런 다음 하나 또는 둘이 아니라 세 개의 테이블 을 넣습니다 . 나는 그들이 결과를 계산하는 간단한 방법으로 이어지기를 희망했지만 대신 다른 모든 사람들이 이미 알고있는 것을 확인했습니다 (OP의 코드가 올바른 일을합니다). 물론 질문은 가독성에 관한 것이지만 가독성은 코드 레이아웃을 수정해야만 달성 할 수는 없습니다 (변경 사항이 기존 코드 표준에 거의 맞지 않음을 인정하십시오). 가독성을 최적화 할 때 논리를 단순화하는 것이 좋습니다.
coredump 2018 년

더 건설적으로 : 나는 세부 사항을 생략하고 답변의 구조에 대해 생각함으로써 답변을 단순화하는 것이 좋습니다. 순열을 생성하는 C ++ 코드를 작성하고 게시하는 데 시간이 걸렸지 만 주 결과와 하나의 테이블 만 제공 할 수 있습니다. 현재는 모든 작업을 그대로 덤프 한 것처럼 보입니다. 나는 TL; DR을 발견하지 못했습니다 (당신은 그것으로 시작할 수 있습니다). 도움이 되길 바랍니다.
coredump

2
건설적인 피드백 코어 덤프에 감사드립니다. 중간 정렬되지 않은 테이블은 쉽게 확인되므로 제거했습니다.
Michaelangel007

2
예수 그리스도! 세 가지 숫자를 비교하는 것이 로켓 과학만큼이나 복잡하다고 누가 말할 수 있습니까?
Mandrill

@Mandrill 컴퓨터 과학자로서 문제를 철저히 이해하는 것이 우리의 임무 입니다. 3 방향 비교를 위해 27 개의 가능한 순열을 모두 열거해야만 솔루션이 모든 경우에 작동 하는지 확인할 수 있습니다 . 프로그래머로서 우리가 원하는 마지막 것은 숨겨진 버그와 설명되지 않은 에지 사례입니다. Verbosity는 정확성에 대한 대가입니다.
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()너무 깨끗하지 않을 수 있지만, 발신자의 관점에서 달성하려고하는 것이 합리적으로 명백하다.
Ken YN

또는 두 개의 배열을 보유하는 대신 하나의 키-값 쌍 배열을 보유하십시오 : {MOSTLY_A, countAs ()}, 값으로 정렬 된 첫 번째 요소를 가져 와서 키를 읽습니다.
Julia Hayward

@ 줄리아 헤이워드 (JuliaHayward) : 그런 해결책을 제안하지 않은 주된 이유는 질문의 "C"태그였습니다 .C에서 키-값 쌍을 처리하고 KVP에 따라 유형이 지정된 함수를 생성하려면 더 많은 상용구 코드가 필요합니다 아마도 간단한 int유형 함수 와 다른 컨텍스트에서 재사용 할 수 없을 것입니다 . 그러나 Python 또는 Perl과 같은 다른 언어를 사용하는 경우 귀하의 의견에 동의합니다.
Doc Brown

1
@ gnasher729 : 원래 코드의 "중복"수, 실제 유사성 및 FindStrictMaxIndex함수 재사용 빈도에 따라 다릅니다 . 한두 번의 재사용으로, 물론 이것은 갚지 않을 것이지만 그것은 이미 내 대답에 쓴 것입니다. 또한 향후 변경과 관련하여 위에서 언급 한 다른 이점에 유의하십시오.
Doc Brown

1
... 그리고 return result[FindStrictMaxIndex(val,3)]; 원래의 8 개 라인이 배치 된 코드에서 원래의 8 개 라인을 간단한 원 라이너로 교체 할 수 있습니다 . 다른 부분들, 특히 FindStrictMaxIndex그 자체는 "비즈니스 로직"과 완전히 분리되어 변화하는 비즈니스 요구 사항의 초점에서 벗어나게됩니다.
Doc Brown

-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) 여러 곳에서 호출 할 수 있습니다. 조금 추한 것입니다. I는 지역 변수를 정의 x, y, z 나쁜 부작용을 낮출 ....


2
공통 논리를 함수로 리팩토링하는 것이 올바른 접근 방법이지만 실제로 두 가지를 피할 수 있습니다. 1. 새로운 요구 사항을 "발명"하지 않습니다 (OP는 최대 값 계산을 요구하지 않았습니다). 그리고 두 번째 : 결과 코드가 더 건조 해지더라도 복잡한 매크로를 정당화하면 논쟁의 여지가 있습니다.
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, bc같은 순서로 확인 용이하고, 매크로 이름은 나에게 모든 비교와 AND 연산 무슨 일을 해결하는 데 절약 할 수 있습니다.


1
(1) 왜 기능 대신 보조 매크로가 필요한가? (2) 왜 시각적 확인이 필요한가요? 실제로 핵심 문제 입니까, 아니면 시각적 확인 이 코드 복제 의 결과 입니까? 가장 좋은 방법은 코드를 하나의 간단한 함수로 분해하여 한 번에 모두 확인 하는 것 입니다.
coredump 2015 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.