web-dev-qa-db-ja.com

3つの数値を比較するシンプルでクリーンな方法

動作するifsのシーケンスを持つコードがいくつかありますが、面倒です。基本的に、私は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(if else)elseを処理するエレガントな方法 は、ネストレベルと非対称性のみを扱いますが、ここでは問題になりません。

11
Ken Y-N

ロジックを因数分解し、早期に戻る

コメントで提案されているように、ロジックを関数にラップし、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を読みやすく、比較を複数回再計算することはありません。

目視確認

あなたの答え であなたは言う:

私の問題はほとんどすべての比較が同じ変数を使用したことの視覚的な確認でした

...また、あなたの質問では、あなたは言う:

このパターンは数回発生し、長い変数名を使用すると、それぞれが正しいかどうかを視覚的に確認することが少し難しくなります。

私はあなたが達成しようとしていることを理解していない可能性があります:必要な場所にパターンをコピーして貼り付けますか?上記のような関数を使用して、パターンを1回キャプチャし、すべての比較で必要に応じてabおよびcが使用されていることをすべて確認します。そうすれば、関数を呼び出すときにもう心配する必要はありません。もちろん、おそらく実際には、あなたの問題はあなたが説明した問題よりも少し複雑です:もしそうなら、可能であればいくつかの詳細を追加してください。

12
coredump

TL:DR;コードは既に正しく「クリーン」です。

たくさんの人が答えを探し回っていますが、誰もが木々の間から森を見逃しています。この質問を完全に理解するために、完全なコンピュータサイエンスと数学的分析を実行してみましょう。

まず、3つの変数があり、それぞれに3つの状態(<、=、または>)があることに注意してください。順列の総数は3 ^ 3 = 27の状態で、P#で示される一意の番号を各状態に割り当てます。このP#番号は factorial number system です。

私たちが持っているすべての順列を列挙します:

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つの状態、
  • A = BまたはB = Cのいずれかである4つの状態。

これらのすべての順列を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つのテストを省略できます。

それが間違った答えを得た場合、コードがどれほどクリーン/悪いかは問題ではないので、これはあなたがすべてのケースを正しく処理していることの良い兆候です!

次に、単純化については、人々は答えを「改善」しようとし続けます。改善とは比較の数を「最適化」することを意味すると考えますが、それは厳密にはあなたが求めていることではありません。あなたは「もっと良いのではないかと思います」とあなたが尋ねたすべての人を混乱させましたが、「良い」の意味を定義していませんでした。比較が少ない?コードが少ない?最適な比較ですか?

コードの可読性(正確性を考慮)について質問しているので、コードを読みやすくするために1つの変更のみを行います。最初のテストを他のテストと揃えます。

        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を明確化、コードをクリーンアップ、不可能な状態について説明。

9
Michaelangel007

@mswはa、b、cの代わりに配列を使用するように指示し、@ Basileは「最大」ロジックを関数にリファクタリングするよう指示しました。これら2つのアイデアを組み合わせると、

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の総数は、元のLOCの数よりも増えたようですが、コアロジックが再利用可能な関数に含まれるようになり、関数を複数回再利用できる場合は効果が現れ始めます。さらに、FindStrictMaxIndex関数は「ビジネス要件」と相互に関連しなくなるため(懸念の分離)、後で変更する必要があるリスクは、元のバージョン(オープンクローズの原則)よりもはるかに低くなります。 )。たとえば、引数の数が変わった場合や、MOSTLY_ABC以外の戻り値を使用する必要がある場合や、a、b、c以外の変数を処理している場合でも、その関数を変更する必要はありません。さらに、3つの異なる値a、b、cの代わりに配列を使用すると、他の場所でもコードが簡略化される場合があります。

もちろん、プログラム全体でこの関数を呼び出す場所が1つまたは2つしかなく、配列に値を保持するためのアプリケーションがない場合は、元のコードをそのままにする(または使用する) @coredumpの改善)。

5
Doc Brown