3つの数値を比較する簡単でわかりやすい方法


11

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


3
私にとって唯一の問題は、コードの繰り返しです。あなたがここに持っているものは読むのが非常に明確です、なぜそれを変えるのですか?a、b、cを取り込んでステータスを返す関数に分割するだけです。気分が良くなる場合は、「インライン」をドロップします。マクロも複雑さもなく、古き良き関数を抽出するだけです。後で作業する必要がある場合は、明確なコードに感謝します。
Jトラナ



#definesの名前は不十分であることに注意してください。a = 40、b = 30、c = 30と考えてください。結果はMOSTLY_Aです。しかし、ほとんどのものは実際にはAではなくBまたはCです。MORE_A(まだあいまいですが、BよりAが多く、Aより多く、または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;
  }
}

上記はもう少し冗長ですが、私見が読みやすく、比較を複数回再計算しません。

目視確認

では、あなたの答えはあなたが言います:

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

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

このパターンは数回発生し、変数名が長いと、各ifが正しいことを視覚的に確認するのが少し難しくなります。

あなたが何を達成しようとしているのか理解できないかもしれません。必要な場所にパターンをコピー&ペーストしたいですか?上記のような機能を使用すると、一度パターンをキャプチャし、あなたはすべての比較が使用するすべてのために一度チェックしabそしてc必要に応じて。そうすれば、関数を呼び出すときにもう心配する必要はありません。もちろん、実際には、問題はあなたが説明した問題よりも少し複雑です。もしそうなら、可能であれば詳細を追加してください。


1
DONT_KNOWに関するコメントがわかりません。cが最小で、aとbが同じ場合はどうなりますか?アルゴリズムは、aがbと同じである間にbが最大であることを返します。
ピーターB

@PieterB返された場合と、場合によっては、MOSTLY_Aまたはが返されたとしても、それは問題ではないと仮定して、私は間違っていました。これは修正されました。ありがとう。MOSTLY_Ca == ca > b
コアダンプ

@DocBrown確かに、ほとんどの利点は早期終了動作から得られます。
コアダンプ

1
+1、今では実際にOPの元のコードよりも改善されています。
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つの状態、
  • 3は、Bが最大である状態、
  • 3は、Cが最大である状態、および
  • 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つのテストを省略することができるように、平等性のテストを気にしません。

間違った答えを受け取った場合、コードがどれだけクリーン/悪いかは関係ないので、これはすべてのケースを正しく処理しているという良い兆候です!

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

これで、コードの読みやすさ(正確さを考慮)について尋ねているので、読みやすさのためにコードに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を明確にし、コードをクリーンアップし、不可能な状態を説明しました。


-1:決定の数を減らすと、コードパスが単純になり、コードが読みやすくなりますか?あなたの議論は明確ではありません。まず、誰もが間違っていると言います。次に、1つまたは2つではなく3つのテーブルを配置します。結果を計算するより簡単な方法につながることを望みましたが、代わりに他の人がすでに知っていることを確認しました(OPのコードは正しいことをします)。確かに問題は読みやすさですが、読みやすさはコードレイアウトを変更するだけでは達成されません(変更は既存のコード標準にほとんど適合しないことを認めます)。可読性を最適化する際にロジックを簡素化することは理にかなっています。
コアダンプ

より建設的に:いくつかの詳細を省き、回答の構造について考えることにより、回答を簡素化することをお勧めします。順列を生成するC ++コードの記述と投稿に時間を割いてくれたことに感謝しますが、主な結果と1つのテーブルのみを提供できる可能性があります。私はほとんどTL; DRを見つけることができませんでした(それから始めることができます)。それが役に立てば幸い。
コアダンプ

2
建設的なフィードバックコアダンプをありがとう。簡単に確認できるため、中央の未ソートのテーブルを削除しました。
Michaelangel007

2
イエス・キリスト!3つの数値を比較することは、ロケット科学とほぼ同じくらい複雑だと誰が言うでしょうか?
マンドリル

@Mandrillコンピューター科学者として、問題を完全に理解することが私たちの仕事です。3者間比較のために27の可能な順列すべてを列挙することによってのみ、ソリューションがすべての場合に機能することを検証できます。プログラマーとして最後に望むのは、隠れたバグと原因不明のエッジケースです。冗長性とは、正確さのために支払う価格です。
Michaelangel007

5

@mswは、a、b、cの代わりに配列を使用するよう指示し、@ Basileは、「max」ロジックを関数にリファクタリングするよう指示しました。これら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の改善)。


私はそれが好きです-の内臓はFindStrictMaxIndex()あまりきれいではないかもしれませんが、呼び出し側の観点から、何が達成されようとしているのかは合理的に明らかです。
ケンYN

または、2つの配列を保持する代わりに、キーと値のペアの配列を1つ保持します:{MOSTLY_A、countAs()}、値で順序付けられた最初の要素を取得し、キーを読み取ります。
ジュリアヘイワード

@JuliaHayward:私がそのような解決策を提案しなかった主な理由は、質問の「C」タグでした-Cでは、キーと値のペアを処理し、KVPで入力された関数を作成するために、さらにボイラープレートコードが必要になりますおそらく、異なるコンテキストで単純なint型付き関数ほど再利用できないでしょう。しかし、PythonやPerlなどの別の言語を使用している場合、あなたのコメントに同意します。
Doc Brown

1
@ gnasher729:元のコードの「重複」の数、実際の類似度、およびFindStrictMaxIndex関数を再利用できる頻度に依存します。もちろん、1回または2回の再利用でこれは報われることはありませんが、それはすでに答えで書いたものです。将来の変更に関して上記で述べた他の利点にも注意してください。
ドックブラウン

1
...そしてreturn result[FindStrictMaxIndex(val,3)]; 、元の8行が配置されたコード内のポイントで、元の8行を単純な1行で置き換えることができることに注意してください。他の部分、特にFindStrictMaxIndexそれ自体は「ビジネスロジック」から完全に分離されているため、ビジネス要件の変化の焦点から外れます。
Doc Brown

-1

おそらくMAX 、最大2つの数値を与えるマクロまたは関数を使用する必要があります。

次に、あなただけが欲しい:

 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) 、いくつかの場所で呼び出すことができます。それは少しugいです。 悪い副作用を減らすためxy、ローカル変数、を定義しましたz...


2
共通ロジックを関数にリファクタリングするのは正しいアプローチですが、ここでは実際に次の2つのことを避けます。2つ目:結果のコードがよりドライになる場合でも、これが複雑なマクロを正当化するかどうかは非常に議論の余地があります。
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? */

各マクロがかかることabそしてc同じ順序で確認することが容易であり、マクロ名は私がすべての比較と論理積が何をしているか動作するように手間を省くことができます。


1
(1)なぜ関数ではなく補助マクロなのか?(2)なぜここで視覚的な確認が必要ですか?それは本当にあなたの中心的な問題ですか、または視覚的な確認の必要性はコードの重複の結果ですか?あなたの最良の選択肢は、コードを単一の単純な関数に分解して、一度チェックすることです
コアダンプ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.