「if」ステートメントが多すぎますか?


263

次のコードは、私が必要とする方法で機能しますが、醜い、過度な、または他の多くのものです。私は数式を見て、いくつかの解決策を書こうとしましたが、結局同じくらいの量のステートメントになります。

この例で私に利益をもたらすような数式はありますか、またはステートメントが受け入れられる場合は16ですか?

コードを説明すると、それは一種の同時ターンベースのゲーム用です。2人のプレーヤーにはそれぞれ4つのアクションボタンがあり、結果は配列(0〜3)から取得されますが、変数「1」と「2」はこれが役立つ場合は何かを割り当てました。結果は、0 =どちらにも勝利せず、1 = p1に勝利し、2 = p2に勝利し、3 =両方に勝利します。

public int fightMath(int one, int two) {

    if(one == 0 && two == 0) { result = 0; }
    else if(one == 0 && two == 1) { result = 0; }
    else if(one == 0 && two == 2) { result = 1; }
    else if(one == 0 && two == 3) { result = 2; }
    else if(one == 1 && two == 0) { result = 0; }
    else if(one == 1 && two == 1) { result = 0; }
    else if(one == 1 && two == 2) { result = 2; }
    else if(one == 1 && two == 3) { result = 1; }
    else if(one == 2 && two == 0) { result = 2; }
    else if(one == 2 && two == 1) { result = 1; }
    else if(one == 2 && two == 2) { result = 3; }
    else if(one == 2 && two == 3) { result = 3; }
    else if(one == 3 && two == 0) { result = 1; }
    else if(one == 3 && two == 1) { result = 2; }
    else if(one == 3 && two == 2) { result = 3; }
    else if(one == 3 && two == 3) { result = 3; }

    return result;
}


9
確かに、ブルートフォースではなく一般化できるロジックがありますか?確かにf(a, b)一般的なケースで答えを出すいくつかの関数がありますか?あなたは計算の論理を説明していないので、すべての答えは豚の口紅だけです。まず、プログラムのロジックを真剣に考え直しますint。アクションにフラグを使用することは非常に古いものです。enumsはロジックを含むことができ、記述的です。これにより、よりモダンな方法でコードを記述できます。
ボリス・ザ・スパイダー

上記のリンクされた彼の別の質問で提供された@Steve Benettの回答を読んだ後、これはデータベースと本質的に同じであるため、これに対する簡単な公式の回答はないと推測できます。私は単純なゲーム(ファイター)を作成していて、ユーザーには4つのボタン(blockHigh(0)、blockLow(1)、attackHigh(2)、attackLow(3))の選択肢があることを元の質問で説明しようとしました。これらの番号は、必要になるまで配列に保持されます。後で、それらは関数「fightMath()」によって使用されます。この関数は、playerOneの選択をplayerTwosに対して呼び出し、結果を返します。実際の衝突検出はありません。
TomFirth 14年

9
回答がありましたら投稿してください。特にコードが関係している場合は、コメントでの詳細な説明に従うのは困難です。この質問をCode Reviewに移行する必要があるかどうかについて話したい場合は、それに関するメタディスカッションがあります。
ジョージストッカー2014年

1
「データベースと同じ」とはどういう意味ですか?これらの値がデータベースにある場合は、そこからプルします。そうでなければ、本当に複雑な場合は、そのままにして、ビジネスロジックのコメントを各行の後に追加して、人々が何が起こっているのか理解できるようにします。それは(私にとって)長くて明示的である方がよい-将来の誰かが何が起こっているのかを理解できる。それをマップに配置するか、8行のコードを保存しようとすると、アップサイドは本当に小さく、ダウンサイズは大きくなります。いつかコードを読む必要がある人にとって、ますます混乱します。
skaz 14年

回答:


600

数式が思いつかない場合は、そのような限られた数の結果にテーブルを使用できます。

final int[][] result = new int[][] {
  { 0, 0, 1, 2 },
  { 0, 0, 2, 1 },
  { 2, 1, 3, 3 },
  { 1, 2, 3, 3 }
};
return result[one][two];

7
これは私が以前にこのソリューションを見たことがないので興味深いです。返された結果を理解できているかどうかはよくわかりませんが、テストを楽しんでいきます。
TomFirth 2014年

4
アサートは必要ありませんIndexOutOfBoundsException。1つ以上のインデックスが範囲外の場合でもJavaはとにかくスローします。
JAB 2014年

43
@JoeHarper読みやすいものが必要な場合は、最初にマジックナンバーを使用せず、マッピングを説明するコメントを付けます。現状では、元のバージョンよりもこのバージョンを好んでいますが、長期的に維持できるものには、列挙型または少なくとも名前付き定数を含むアプローチを使用します。
JAB 2014年

13
@JoeHarper「理論的に」は1つのことであり、「実用的に」は別のものです。もちろん、わかりやすい名前を付けようとします(ループ変数のi/ j/ k規則を除く)、名前付き定数、コードを読みやすいように配置するなどですが、変数と関数の名前が20文字を超える場合実際に見つけるたびに、コードが読みにくくなります。私の通常のアプローチは、コードがありのままに構造化されている理由を説明するために、わかりやすいが簡潔なコードをあちこちにコメントすることです。名前に理由を入れると、すべてが雑然とします。
JAB 2014年

13
結果は実際には結果マトリックスによって決定されるため、この特定の問題に対するこのソリューションが好きです。
試行

201

データセットが非常に小さいため、すべてを1つの長整数に圧縮して式に変換できます

public int fightMath(int one,int two)
{
   return (int)(0xF9F66090L >> (2*(one*4 + two)))%4;
}

よりビットごとのバリアント:

これは、すべてが2の倍数であるという事実を利用しています

public int fightMath(int one,int two)
{
   return (0xF9F66090 >> ((one << 3) | (two << 1))) & 0x3;
}

魔法定数の起源

何と言えばいい?世界は魔法を必要とし、時々何かの可能性はその創造を必要とします。

OPの問題を解決する関数の本質は、2つの数値(1、2)、ドメイン{0、1、2、3}から範囲{0、1、2、3}へのマップです。それぞれの答えは、そのマップを実装する方法に近づきました。

また、いくつかの回答で、問題の再説明を1桁が1、2桁が2、N = 4 * oneである、2桁のベース4番号N(one、two)のマップとして確認できます。 + 2; N = {0,1,2、...、15}-16の異なる値、それは重要です。この関数の出力は、1桁の4桁の数値{0,1,2,3}です。4つの異なる値も重要です。

現在、1桁の基数4の数値は、2桁の基数2の数値として表現できます。{0,1,2,3} = {00,01,10,11}なので、各出力は2ビットのみでエンコードできます。上記の場合、可能な出力は16種類しかないため、マップ全体をエンコードするには16 * 2 = 32ビットで十分です。これはすべて1つの整数に収まります。

定数Mはマップmのエンコーディングで、m(0)はビットM [0:1]にエンコードされ、m(1)はビットM [2:3]にエンコードされ、m(n)はビットにエンコードされます。 M [n * 2:n * 2 + 1]。

残っているのは、定数の正しい部分にインデックスを付けて返すことだけです。この場合、Mを右に2 * N回シフトして、2つの最下位ビット、つまり(M >> 2 * N)&0x3を取ることができます。式(1つ<< 3)および(2つ<< 1)は、2 * x = x << 1および8 * x = x << 3であることに注意しながら、単に乗算を行っています。


79
賢いですが、コードを読んでいる他の誰もそれを理解する機会はありません。
アランMulholland 14年

106
これは非常に悪い習慣だと思います。著者以外の誰もそれを理解しません。コードを見て、すぐに理解したいとします。しかし、これは時間の無駄です。
バラージュネメス

14
私はこれについて@BalázsMáriaNémethと一緒にいます。非常に印象的ですが、暴力的なサイコパスをコーディングする必要があります。
OrhanC1 2014年

90
すべての反対投票者は、これは恐ろしいコードのにおいだと思っています。すべての賛成者は同じことを考えていますが、その背後にある賢さを賞賛します。+1(このコードは使用しないでください。)
usr


98

JAB以外の提示された解決策は好きではありません。 他のどれもコードを読み、計算されているものを理解することを容易にしません

これが私がこのコードを書く方法です-私はJavaではなくC#だけを知っています、しかしあなたは絵を得るでしょう:

const bool t = true;
const bool f = false;
static readonly bool[,] attackResult = {
    { f, f, t, f }, 
    { f, f, f, t },
    { f, t, t, t },
    { t, f, t, t }
};
[Flags] enum HitResult 
{ 
    Neither = 0,
    PlayerOne = 1,
    PlayerTwo = 2,
    Both = PlayerOne | PlayerTwo
}
static HitResult ResolveAttack(int one, int two)
{
    return 
        (attackResult[one, two] ? HitResult.PlayerOne : HitResult.Neither) | 
        (attackResult[two, one] ? HitResult.PlayerTwo : HitResult.Neither);
}    

これで、ここで何が計算されているのかがはるかに明確になりました。これは、どの攻撃に誰がヒットしたかを計算し、両方の結果を返すことを強調しています。

しかし、これはさらに良いかもしれません。そのブール配列はやや不透明です。私はテーブルルックアップアプローチが好きですが、意図するゲームセマンティクスが何であるかを明確にするような方法で書く傾向があります。つまり、「0の攻撃と1の防御はヒットしない」ではなく、「ローキック攻撃と低いブロック防御がヒットしない」ことをコードに明確に示す方法を見つけます。 コードにゲームのビジネスロジックを反映させます。


66
ナンセンス。最も穏やかな経験を持つプログラムは、ここで与えられているアドバイスを理解し、コーディングスタイルを独自の言語に適用することができます。問題は、一連のifを回避する方法でした。これは方法を示しています。
GreenAsJade 2014年

6
@ user3414693:Javaの質問であることはよく知っています。答えを注意深く読んだ場合、それは明らかになります。私の答えが賢明ではないと信じているなら、私はあなたがあなたがもっと好きなあなた自身の答えを書くことを勧めます。
Eric Lippert

1
@EricLippert私もJABのソリューションが好きです。私見、C#の列挙型では、多くのことが望まれます。それは、残りの機能が行う成功哲学の要点には従いません。例:stackoverflow.com/a/847353/92414 c#チームは、より優れた設計の新しい列挙型を(既存のコードを壊さないように)作成する予定はありますか?
SolutionYogi

@SolutionYogi:C#の列挙型もあまり好きではありませんが、歴史的な理由からそうです。(主に既存のCOMの列挙型との互換性のため。)私はC#6に列挙型のための新しいギアを追加する計画を認識していないよ
エリックリペット

3
@SListいいえ、コメントは実行されません。OPは正確に何をすべきかを行いました。コメントを明確なコードに変換します。たとえば、Steve McConnellのコードを 参照してくださいstevemcconnell.com/cccntnt.htmの
djechlin

87

結果を含むマトリックスを作成できます

int[][] results = {{0, 0, 1, 2}, {0, 0, 2, 1},{2, 1, 3, 3},{2, 1, 3, 3}};

価値を得たいときは

public int fightMath(int one, int two) {
  return this.results[one][two]; 
}

69

他の人々はすでに私の最初のアイデアであるマトリックス法を提案していますが、ifステートメントを統合することに加えて、提供された引数が期待される範囲内にあることを確認し、インプレースリターン(一部のコーディング私が見た標準は関数に1つの出口を強制しますが、複数の戻り値は矢印コーディングを回避するのに非常に役立ち、Javaで例外が蔓延しているため、とにかくそのようなルールを厳密に適用することにはあまり意味がないことがわかりましたメソッド内でスローされたキャッチされない例外は、とにかく出口の可能性があるためです)。switchステートメントをネストすることは可能ですが、ここで確認している値の範囲が狭い場合は、ステートメントがよりコンパクトで、パフォーマンスに大きな違いが出ない可能性があるかどうかを確認します。

public int fightMath(int one, int two) {
    if (one > 3 || one < 0 || two > 3 || two < 0) {
        throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
    }

    if (one <= 1) {
        if (two <= 1) return 0;
        if (two - one == 2) return 1;
        return 2; // two can only be 3 here, no need for an explicit conditional
    }

    // one >= 2
    if (two >= 2) return 3;
    if (two == 1) return 1;
    return 2; // two can only be 0 here
}

これは、input-> resultマッピングの一部の不規則性が原因である可能性があるため、最終的には読みにくくなります。私は代わりに、マトリックススタイルを好んでいます。その単純さと、マトリックスを視覚的に設定して視覚的に理解できるようにする方法です(ただし、これはKarnaughマップの私の記憶の影響を受けています)。

int[][] results = {{0, 0, 1, 2},
                   {0, 0, 2, 1},
                   {2, 1, 3, 3},
                   {2, 1, 3, 3}};

更新:ブロッキング/ヒッティングについて言及した場合、入力と結果にプロパティ/属性保持列挙型を使用し、ブロッキングを説明するために結果を少し変更する関数に、より根本的な変更があります。読み取り可能な関数。

enum MoveType {
    ATTACK,
    BLOCK;
}

enum MoveHeight {
    HIGH,
    LOW;
}

enum Move {
    // Enum members can have properties/attributes/data members of their own
    ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
    ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
    BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
    BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);

    public final MoveType type;
    public final MoveHeight height;

    private Move(MoveType type, MoveHeight height) {
        this.type = type;
        this.height = height;
    }

    /** Makes the attack checks later on simpler. */
    public boolean isAttack() {
        return this.type == MoveType.ATTACK;
    }
}

enum LandedHit {
    NEITHER,
    PLAYER_ONE,
    PLAYER_TWO,
    BOTH;
}

LandedHit fightMath(Move one, Move two) {
    // One is an attack, the other is a block
    if (one.type != two.type) {
        // attack at some height gets blocked by block at same height
        if (one.height == two.height) return LandedHit.NEITHER;

        // Either player 1 attacked or player 2 attacked; whoever did
        // lands a hit
        if (one.isAttack()) return LandedHit.PLAYER_ONE;
        return LandedHit.PLAYER_TWO;
    }

    // both attack
    if (one.isAttack()) return LandedHit.BOTH;

    // both block
    return LandedHit.NEITHER;
}

列挙型だけで、より高さのあるブロック/攻撃を追加する場合は、関数自体を変更する必要さえありません。ただし、移動のタイプを追加するには、おそらく関数の変更が必要になります。また、EnumSet Sは、例えば、メイン列挙型の性質として、余分な列挙型を使用するよりも拡張可能であるかもしれないEnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);し、その後attacks.contains(move)ではなく、move.type == MoveType.ATTACK使用していますが、EnumSetSは、おそらく直接対等チェックよりもわずかに遅くなります。


カウンターで成功したブロックの結果の場合には、あなたは置き換えることができif (one.height == two.height) return LandedHit.NEITHER;

if (one.height == two.height) {
    // Successful block results in a counter against the attacker
    if (one.isAttack()) return LandedHit.PLAYER_TWO;
    return LandedHit.PLAYER_ONE;
}

また、一部のifステートメントを三項演算子(boolean_expression ? result_if_true : result_if_false)を使用して置き換えると、コードがよりコンパクトになります(たとえば、前のブロックのコードはreturn one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;)が、読みにくいonelinersにつながる可能性があるため、より複雑な分岐にはこれをお勧めします。


私は間違いなく、このになりますが、私の現在のコードは私がのint型の値を使用することができますoneし、two私のspritesheet上の開始点として再利用します。それを可能にするためにそれは多くの追加のコードを必要としませんが。
TomFirth 2014年

2
@ TomFirth84ありますEnumMapあなたは、整数オフセットに列挙型をマップするために使用できることをクラスでは、(あなたも例えば、直接列挙メンバーの順序値を使用することができますMove.ATTACK_HIGH.ordinal()だろう0Move.ATTACK_LOW.ordinal()だろう1、などが、それはより脆弱な/柔軟性に劣る明示的によります既存のメンバーの間に列挙値を追加するときに各メンバーを値に関連付けると、カウントがスローされます。これは、EnumMap。の場合とは異なります。)
JAB

7
これは、コードをコードを読む人にとって意味のあるものに変換するため、最も読みやすいソリューションです。
David Stanley

あなたのコード、少なくとも列挙型を使用しているコードは間違っています。OPのifステートメントによると、成功したブロックは攻撃者にヒットをもたらします。ただし、意味のあるコードの場合は+1。
Taemyr、2014年

2
列挙型にattack(against)メソッドを追加してMove、移動が成功した攻撃の場合はHITを返し、移動がブロックされた攻撃の場合はBACKFIREを返し、攻撃でない場合はNOTHINGを返すこともできます。このようにして、一般的にそれを実装し(public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; })、必要に応じて特定の移動(任意のブロックがブロックできる弱い移動、バックファイアしない攻撃など)でオーバーライドできます
書き換え

50

なぜ配列を使用しないのですか?

最初から始めます。パターンが表示されます。値は0から3まであり、すべての可能な値をキャッチしたいと思います。こちらがお席で御座います:

0 & 0 = 0
0 & 1 = 0
0 & 2 = 1
0 & 3 = 2
1 & 0 = 0
1 & 1 = 0
1 & 2 = 2
1 & 3 = 1
2 & 0 = 2
2 & 1 = 1
2 & 2 = 3
2 & 3 = 3
3 & 0 = 2
3 & 1 = 1
3 & 2 = 3
3 & 3 = 3

この同じテーブルバイナリを見ると、次の結果がわかります。

00 & 00 = 00
00 & 01 = 00
00 & 10 = 01
00 & 11 = 10
01 & 00 = 00
01 & 01 = 00
01 & 10 = 10
01 & 11 = 01
10 & 00 = 10
10 & 01 = 01
10 & 10 = 11
10 & 11 = 11
11 & 00 = 10
11 & 01 = 01
11 & 10 = 11
11 & 11 = 11

おそらくすでにいくつかのパターンが表示されていますが、値1と2を組み合わせると、すべての値0000、0001、0010、..... 1110と1111を使用していることがわかります。次に、値1と2を組み合わせて1つにします4ビット整数。

0000 = 00
0001 = 00
0010 = 01
0011 = 10
0100 = 00
0101 = 00
0110 = 10
0111 = 01
1000 = 10
1001 = 01
1010 = 11
1011 = 11
1100 = 10
1101 = 01
1110 = 11
1111 = 11

これを10進値に戻すと、1と2を組み合わせたものをインデックスとして使用できる非常に可能な値の配列が表示されます。

0 = 0
1 = 0
2 = 1
3 = 2
4 = 0
5 = 0
6 = 2
7 = 1
8 = 2
9 = 1
10 = 3
11 = 3
12 = 2
13 = 1
14 = 3
15 = 3

次に{0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3}、配列はであり、そのインデックスは単純に1と2を組み合わせたものです。

私はJavaプログラマーではありませんが、すべてのifステートメントを削除して、次のように書き留めることができます。

int[] myIntArray = {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3};
result = myIntArray[one * 4 + two]; 

2のビットシフトが乗算より速いかどうかはわかりません。しかし、それは試してみる価値があるかもしれません。


2
2のビットシフトは、4の乗算よりもほぼ確実に高速です。せいぜい、4による乗算は、4が2 ^ 2であることを認識し、ビットシフト自体を実行します(コンパイラによって変換される可能性があります)。率直に言って、私にとって、シフトはより読みやすくなっています。
ランチャー、2014年

私はあなたのアプローチが好きです!基本的に、4x4マトリックスを16要素の配列にフラット化します。
Cameron Tinker 14年

6
今日、私が間違っていなければ、コンパイラは間違いなく2のべき乗を掛けていることを認識し、それに応じて最適化します。したがって、プログラマーにとって、ビットシフトと乗算はまったく同じパフォーマンスを持つ必要があります。
Tanner Swett、

24

これは少しビットマジックを使用します(1つの整数に2ビットの情報(低/高&攻撃/ブロック)を保持することですでに実行しています):

実行していません。ここに入力しただけです。再確認してください。アイデアは確かに機能します。 編集:すべての入力に対してテストされ、正常に動作します。

public int fightMath(int one, int two) {
    if(one<2 && two<2){ //both players blocking
        return 0; // nobody hits
    }else if(one>1 && two>1){ //both players attacking
        return 3; // both hit
    }else{ // some of them attack, other one blocks
        int different_height = (one ^ two) & 1; // is 0 if they are both going for the same height - i.e. blocker wins, and 1 if height is different, thus attacker wins
        int attacker = one>1?1:0; // is 1 if one is the attacker, two is the blocker, and 0 if one is the blocker, two is the attacker
        return (attacker ^ different_height) + 1;
    }
}

または、2つの情報を別々の変数に分離することをお勧めしますか?上記のようなビット演算に基づいたコードは、通常、維持するのが非常に困難です。


2
私はこの解決策に同意します。メインの質問に対する私のコメントで私が考えていたものとよく似ています。私はそれを別々の変数に分割したいと思います。これにより、たとえば将来的に中間攻撃を追加することが容易になります。
Yoh

2
テストした後、上のコードのいくつかのバグを修正しましたが、今はうまく機能しています。私も他の回答でビットマスクとして神秘的とはまだないが、それでもあなたの心をネジ止めするためのトリッキー十分である1つのラインソリューション、思い付いたビット・マニピュレータの道をさらに行く:return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
エリアス

1
これを読んでいる新しいプログラマーは、実際にそれらすべてのマジック番号の背後で起こっているマジックを理解するため、これが最良の答えです。
bezmax 14年

20

正直に言うと、誰もが独自のスタイルのコードを持っています。パフォーマンスにあまり影響しないとは思いませんでした。スイッチケースバージョンを使用するよりもこれをよく理解している場合は、これを使用してください。

ifsをネストすることができるため、最後のifチェックでは、ifステートメントをあまり実行しなかったため、パフォーマンスがわずかに向上する可能性があります。ただし、基本的なJavaコースのコンテキストでは、おそらくメリットはありません。

else if(one == 3 && two == 3) { result = 3; }

したがって、代わりに...

if(one == 0 && two == 0) { result = 0; }
else if(one == 0 && two == 1) { result = 0; }
else if(one == 0 && two == 2) { result = 1; }
else if(one == 0 && two == 3) { result = 2; }

あなたがするだろう...

if(one == 0) 
{ 
    if(two == 0) { result = 0; }
    else if(two == 1) { result = 0; }
    else if(two == 2) { result = 1; }
    else if(two == 3) { result = 2; }
}

必要に応じて再フォーマットしてください。

これによってコードの見栄えがよくなるわけではありませんが、コードが少しスピードアップする可能性があると思います。


3
それが本当に良い習慣かどうかはわかりませんが、この場合、おそらく入れ子になったswitchステートメントを使用します。より多くのスペースが必要になりますが、それは本当に明確になります。
dyesdyes 2014年

それもうまくいきますが、好みの問題だと思います。コードが何をしているかを事実上言っているので、私は実際にはifステートメントを好みます。もちろん、あなたの意見を書き留めないでください。代わりの提案にも賛成投票してください!
ジョーハーパー

12

私たちが知っていることを見てみましょう

1:P1(プレーヤー1)とP2(プレーヤー2)の回答は対称的です。これは格闘ゲームでは理にかなっていますが、ロジックを改善するために利用できるものでもあります。

2:3ビート0ビート2ビート1ビート3.これらのケースでカバーされない唯一のケースは、0 vs 1と2 vs 3の組み合わせです。言い換えると、ユニークな勝利テーブルは次のようになります:0ビート2、1ビート3、2ビート1、3ビート0。

3:0/1が互いに対抗する場合、ヒットレスドローがありますが、2/3が対抗する場合、両方がヒットします

まず、勝ったかどうかを通知する一方向関数を作成します。

// returns whether we beat our opponent
public boolean doesBeat(int attacker, int defender) {
  int[] beats = {2, 3, 1, 0};
  return defender == beats[attacker];
}

次に、この関数を使用して最終結果を作成できます。

// returns the overall fight result
// bit 0 = one hits
// bit 1 = two hits
public int fightMath(int one, int two)
{
  // Check to see whether either has an outright winning combo
  if (doesBeat(one, two))
    return 1;

  if (doesBeat(two, one))
    return 2;

  // If both have 0/1 then its hitless draw but if both have 2/3 then they both hit.
  // We can check this by seeing whether the second bit is set and we need only check
  // one's value as combinations where they don't both have 0/1 or 2/3 have already
  // been dealt with 
  return (one & 2) ? 3 : 0;
}

これは間違いなく複雑であり、多くの回答で提供されているテーブル検索よりもおそらく遅いですが、実際にはコードのロジックをカプセル化し、コードを読んでいる人に説明するため、これは優れた方法だと思います。これはより良い実装になると思います。

(構文がオフの場合はJavaの謝罪を行ってからしばらく経っていますが、少し間違っていても理解できると思います)

ちなみに、0-3は明らかに何かを意味します。これらは任意の値ではないため、名前を付けると役立ちます。


11

論理が正しく理解できればと思います。次のようなものはどうですか:

public int fightMath (int one, int two)
{
    int oneHit = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : 0;
    int twoHit = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : 0;

    return oneHit+twoHit;
}

1ヒット高または1ヒット低のチェックはブロックされず、プレーヤー2でも同じです。

編集:アルゴリズムは完全には理解されておらず、私が気付かなかったブロック時に「ヒット」を獲得しました(Thxエリアス):

public int fightMath (int one, int two)
{
    int oneAttack = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : (one >= 2) ? 2 : 0;
    int twoAttack = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : (two >= 2) ? 1 : 0;

    return oneAttack | twoAttack;
}

パターンの素晴らしい結果を見つける方法!
チャド

1
私はこのアプローチが好きですが、このソリューションは攻撃をブロックすることによる攻撃の可能性を完全に欠いていると思います(たとえば、one = 0およびtwo = 2の場合、0を返しますが、仕様によれば1が期待されます)。多分あなたはそれを正しくするために取り組むことができますが、結果のコードがこれほどエレガントであることはわかりません、これは行が少し長くなることを意味します。
エリアス2014年

「ヒット」がブロックに与えられたことに気づかなかった。指摘してくれてありがとう。非常に簡単な修正で調整。
Chris

10

私はJavaの経験がないので、タイプミスがあるかもしれません。コードを疑似コードと見なしてください。

簡単なスイッチで行きます。そのためには、単一の数値評価が必要になります。ただし、この場合、0 <= one < 4 <= 9およびなので、10を0 <= two < 4 <= 9乗算oneしてを追加することにより、両方のintを単純なintに変換できますtwo。次に、次のように結果の数値にスイッチを使用します。

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 10
    int evaluate = one * 10 + two;

    switch(evaluate) {
        // I'd consider a comment in each line here and in the original code
        // for clarity
        case 0: result = 0; break;
        case 1: result = 0; break;
        case 1: result = 0; break;
        case 2: result = 1; break;
        case 3: result = 2; break;
        case 10: result = 0; break;
        case 11: result = 0; break;
        case 12: result = 2; break;
        case 13: result = 1; break;
        case 20: result = 2; break;
        case 21: result = 1; break;
        case 22: result = 3; break;
        case 23: result = 3; break;
        case 30: result = 1; break;
        case 31: result = 2; break;
        case 32: result = 3; break;
        case 33: result = 3; break;
    }

    return result;
}

理論的なコードとして指摘したい別の短い方法があります。ただし、通常は処理したくない余分な複雑さがあるため、使用しません。カウントが0、1、2、3、10、11、12、13、20、...であるため、追加の複雑さは基数4から来ます

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 4
    int evaluate = one * 4 + two;

    allresults = new int[] { 0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 1, 2, 3, 3 };

    return allresults[evaluate];
}

Javaから何かが欠けている場合に備えて、本当に追加の注意事項です。PHPでは、次のようにします。

function fightMath($one, $two) {
    // Convert one and two to a single variable in base 4
    $evaluate = $one * 10 + $two;

    $allresults = array(
         0 => 0,  1 => 0,  2 => 1,  3 => 2,
        10 => 0, 11 => 0, 12 => 2, 13 => 1,
        20 => 2, 21 => 1, 22 => 3, 23 => 3,
        30 => 1, 31 => 2, 32 => 3, 33 => 3 );

    return $allresults[$evaluate];
}

Javaは第8バージョンの前にラムダを持たない
Kirill Gamazkov

1
この。このような少数の入力については、複合値のスイッチを使用します(ただし、100や1000など、10より大きい乗数を使用すると読みやすくなります)。
Medinoc 2014年

7

ネストされたif条件を好むので、ここに別の方法があります。メンバーを
使用resultせず、状態を変更しないことに注意してください。

public int fightMath(int one, int two) {
    if (one == 0) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 1; }
      if (two == 3) { return 2; }
    }   
    if (one == 1) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 2; }
      if (two == 3) { return 1; }
    }
    if (one == 2) {
      if (two == 0) { return 2; }
      if (two == 1) { return 1; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    if (one == 3) {
      if (two == 0) { return 1; }
      if (two == 1) { return 2; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    return DEFAULT_RESULT;
}

なんで他にないの?
FDinoff 2014年

3
@FDinoff elseチェーンを使用することもできましたが、違いはありませんでした。
Nick Dandoulakis 2014年

1
私はそれが取るに​​足らないことを知っていますが、elseチェーンを追加すると実行が速くなりませんか?4ケース中3ケース?私は常に、たとえ数サイクルしかない場合でも、できるだけ速く実行するコードを書く習慣をつけています。
Brandon Bearden

2
ここで@BrandonBeardenを使用しても、違いはありません(入力が常に0..3の範囲にあると想定しています)。コンパイラはおそらくとにかくコードをマイクロ最適化します。長い一連のelse ifステートメントがある場合は、switchテーブルのルックアップまたはルックアップを使用してコードを高速化できます。
Nick Dandoulakis 2014年

どうですか?場合one==0は、コードを実行する、それがどうかをチェックする必要がありますone==1場合は、その後one==2、最終的にもしone==3-もしさんがそこに他があった場合、それは最初の試合後のステートメントを終了してしまうため、それが最後の三つのチェックをしないだろう。そして、はい、ステートメントの代わりにswitchステートメントを使用し、if (one...さらにのケース内で別のスイッチを使用することで、さらに最適化できますone's。しかし、それは私の質問ではありません。
Brandon Bearden

6

スイッチケーシングでそれを試してください..

見てください、ここここでそれについての詳細は

switch (expression)
{ 
  case constant:
        statements;
        break;
  [ case constant-2:
        statements;
        break;  ] ...
  [ default:
        statements;
        break;  ] ...
}

あなたはそれに(同時にではなく)複数の条件追加することができ、デフォルトのオプションさえあります他のケースが満たされていません。

PS:1つの条件が満たされる場合のみ。

2つの条件が同時に発生した場合..スイッチは使用できないと思います。ただし、ここでコードを減らすことができます。

Java switchステートメントの複数のケース


6

私に最初に起こったのは、Francisco Presenciaによって与えられた本質的に同じ答えでしたが、多少最適化されました。

public int fightMath(int one, int two)
{
    switch (one*10 + two)
    {
    case  0:
    case  1:
    case 10:
    case 11:
        return 0;
    case  2:
    case 13:
    case 21:
    case 30:
        return 1;
    case  3:
    case 12:
    case 20:
    case 31:
        return 2;
    case 22:
    case 23:
    case 32:
    case 33:
        return 3;
    }
}

最後のケース(3の場合)をデフォルトのケースにすることで、さらに最適化できます。

    //case 22:
    //case 23:
    //case 32:
    //case 33:
    default:
        return 3;

この方法の利点は、の値かを確認することが容易であることであるonetwo他の提案方法のいくつかよりも、対応する戻り値。


これは私の別の答えのバリエーションです
David R Tribble

6
((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2

4
これにたどり着くまでどのくらいかかりましたか。
mbatchkarov 14年

2
@mbatchkarov他の解答を約10分間読んだ後、鉛筆と紙で10分間落書きします。
Dawood ibnカリーム2014年

7
これを維持しなければならないとしたら本当に悲しいです。
Meryovi 2014年

ええと...バグがあります:あなたが行方不明です; --unicorns
アルベルト

私は@Meryoviに同意します。簡潔であるための小道具でありながら、APLコードのようにひどいものです
Ed Griebel '26年


3

one / twoとその結果の間でテーブルを描くと、1つのパターンが表示されます。

if(one<2 && two <2) result=0; return;

上記の場合、少なくとも3つのifステートメントが削減されます。セットパターンが表示されず、指定されたコードから多くを収集することもできませんが、そのようなロジックを導出できれば、ifステートメントの数が減ります。

お役に立てれば。


3

ルールをテキストとして定義すると、正しい式を簡単に導出できます。これは、laaltoの優れた配列表現から抽出されます。

{ 0, 0, 1, 2 },
{ 0, 0, 2, 1 },
{ 2, 1, 3, 3 },
{ 1, 2, 3, 3 }

ここでは、一般的なコメントをいくつか示しますが、ルールの用語で説明する必要があります。

if(one<2) // left half
{
    if(two<2) // upper left half
    {
        result = 0; //neither hits
    }
    else // lower left half
    {
        result = 1+(one+two)%2; //p2 hits if sum is even
    }
}
else // right half
{
    if(two<2) // upper right half
    {
        result = 1+(one+two+1)%2; //p1 hits if sum is even
    }
    else // lower right half
    {
        return 3; //both hit
    }
}

もちろん、これをより少ないコードにまで減らすこともできますが、コンパクトなソリューションを見つけるよりも、コーディングする内容を理解することをお勧めします。

if((one<2)&&(two<2)) result = 0; //top left
else if((one>1)&&(two>1)) result = 3; //bottom right
else result = 1+(one+two+((one>1)?1:0))%2; //no idea what that means

複雑なp1 / p2ヒットについてのいくつかの説明は素晴らしいでしょう、興味深いようです!


3

最短で読みやすいソリューション:

static public int fightMath(int one, int two)
{
    if (one < 2 && two < 2) return 0;
    if (one > 1 && two > 1) return 3;
    int n = (one + two) % 2;
    return one < two ? 1 + n : 2 - n;
}

またはさらに短い:

static public int fightMath(int one, int two)
{
    if (one / 2 == two / 2) return (one / 2) * 3;
    return 1 + (one + two + one / 2) % 2;
}

「マジック」番号は含まれていません;)それがお役に立てば幸いです。


2
このような数式を使用すると、組み合わせの結果を後日変更(更新)できなくなります。唯一の方法は、式全体を作り直すことです。
SNag 2014年

2
@SNag:私はそれに同意します。最も柔軟なソリューションは、2Dアレイを使用することです。しかし、この投稿の執筆者は公式を求めていました。これは、単純なifsと数学だけで取得できる最高のものです。
PW

2

static int val(int i, int u){ int q = (i & 1) ^ (u & 1); return ((i >> 1) << (1 ^ q))|((u >> 1) << q); }


1

私は個人的に三項演算子をカスケードするのが好きです:

int result = condition1
    ? result1
    : condition2
    ? result2
    : condition3
    ? result3
    : resultElse;

しかし、あなたの場合、あなたは使うことができます:

final int[] result = new int[/*16*/] {
    0, 0, 1, 2,
    0, 0, 2, 1,
    2, 1, 3, 3,
    1, 2, 3, 3
};

public int fightMath(int one, int two) {
    return result[one*4 + two];
}

または、ビットのパターンに気付くことができます:

one   two   result

section 1: higher bits are equals =>
both result bits are equals to that higher bits

00    00    00
00    01    00
01    00    00
01    01    00
10    10    11
10    11    11
11    10    11
11    11    11

section 2: higher bits are different =>
lower result bit is inverse of lower bit of 'two'
higher result bit is lower bit of 'two'

00    10    01
00    11    10
01    10    10
01    11    01
10    00    10
10    01    01
11    00    01
11    01    10

だからあなたは魔法を使うことができます:

int fightMath(int one, int two) {
    int b1 = one & 2, b2 = two & 2;
    if (b1 == b2)
        return b1 | (b1 >> 1);

    b1 = two & 1;

    return (b1 << 1) | (~b1);
}

1

これは、JABの応答に似た、かなり簡潔なバージョンです。これは、勝利を他の人よりも上に移動させるためにマップを利用します。

public enum Result {
  P1Win, P2Win, BothWin, NeitherWin;
}

public enum Move {
  BLOCK_HIGH, BLOCK_LOW, ATTACK_HIGH, ATTACK_LOW;

  static final Map<Move, List<Move>> beats = new EnumMap<Move, List<Move>>(
      Move.class);

  static {
    beats.put(BLOCK_HIGH, new ArrayList<Move>());
    beats.put(BLOCK_LOW, new ArrayList<Move>());
    beats.put(ATTACK_HIGH, Arrays.asList(ATTACK_LOW, BLOCK_LOW));
    beats.put(ATTACK_LOW, Arrays.asList(ATTACK_HIGH, BLOCK_HIGH));
  }

  public static Result compare(Move p1Move, Move p2Move) {
    boolean p1Wins = beats.get(p1Move).contains(p2Move);
    boolean p2Wins = beats.get(p2Move).contains(p1Move);

    if (p1Wins) {
      return (p2Wins) ? Result.BothWin : Result.P1Win;
    }
    if (p2Wins) {
      return (p1Wins) ? Result.BothWin : Result.P2Win;
    }

    return Result.NeitherWin;
  }
} 

例:

System.out.println(Move.compare(Move.ATTACK_HIGH, Move.BLOCK_LOW));

プリント:

P1Win

static final Map<Move, List<Move>> beats = new java.util.EnumMap<>();代わりに私はお勧めします、それは少し効率的である必要があります。
JAB 2014年

@JABはい、いい考えです。そのタイプが存在することをいつも忘れています。そして...それを構築するのはどれほど厄介です!
ダンカンジョーンズ

1

HashMapまたはTreeMapのいずれかのマップを使用します

特にパラメータがフォーム上にない場合 0 <= X < N

ランダムな正の整数のセットのように..

コード

public class MyMap
{
    private TreeMap<String,Integer> map;

    public MyMap ()
    {
        map = new TreeMap<String,Integer> ();
    }

    public void put (int key1, int key2, Integer value)
    {
        String key = (key1+":"+key2);

        map.put(key, new Integer(value));
    }

    public Integer get (int key1, int key2)
    {
        String key = (key1+":"+key2);

        return map.get(key);
    }
}

1

最終的には彼の回答のバリエーションを使用するようになった@Joe Harperに感謝します。4あたり2つの結果が同じだったので、さらにスリム化するには、さらにスリム化しました。

私はifいつかこれに戻るかもしれませんが、複数のステートメントによって引き起こされる大きな抵抗がなければ、今のところこれを保持します。テーブルマトリックスを調べ、ステートメントソリューションをさらに切り替えます。

public int fightMath(int one, int two) {
  if (one === 0) {
    if (two === 2) { return 1; }
    else if(two === 3) { return 2; }
    else { return 0; }
  } else if (one === 1) {
    if (two === 2) { return 2; }
    else if (two === 3) { return 1; }
    else { return 0; }
  } else if (one === 2) {
    if (two === 0) { return 2; }
    else if (two === 1) { return 1; }
    else { return 3; }
  } else if (one === 3) {
    if (two === 0) { return 1; }
    else if (two === 1) { return 2; }
    else { return 3; }
  }
}

13
これは実際にはオリジナルよりも読みにくく、ifステートメントの数を減らしません...
Chad

@Chadこれのアイデアはプロセス速度を上げることでした。恐ろしいことに見えますが、今後さらにアクションを追加する場合は簡単に更新できます。これを言って、私は今私が以前完全に理解していなかった以前の答えを使用しています。
TomFirth 2014年

3
@ TomFirth84 ifステートメントの適切なコーディング規則に従っていない理由はありますか?
ylun.ca 2014年

@ylun:SOに貼り付ける前に行を減らしました。読みやすさのためではなく、純粋なスパムスペースのためです。このページには練習のバリエーションがあり、残念ながら、これは私が学んだ方法であり、慣れています。
TomFirth 2014年

2
@ TomFirth84 これは簡単には更新されないと思います。行の数は、許可された値の数の積のように大きくなります。
Andrew Lazarus 14年

0
  1. 定数または列挙型を使用してコードを読みやすくする
  2. コードをより多くの関数に分割してみてください
  3. 問題の対称性を使用してみてください

これがどのように見えるかを提案しますが、ここでintを使用することはまだ醜いです

static final int BLOCK_HIGH = 0;
static final int BLOCK_LOW = 1;
static final int ATTACK_HIGH = 2;
static final int ATTACK_LOW = 3;

public static int fightMath(int one, int two) {
    boolean player1Wins = handleAttack(one, two);
    boolean player2Wins = handleAttack(two, one);
    return encodeResult(player1Wins, player2Wins); 
}



private static boolean handleAttack(int one, int two) {
     return one == ATTACK_HIGH && two != BLOCK_HIGH
        || one == ATTACK_LOW && two != BLOCK_LOW
        || one == BLOCK_HIGH && two == ATTACK_HIGH
        || one == BLOCK_LOW && two == ATTACK_LOW;

}

private static int encodeResult(boolean player1Wins, boolean player2Wins) {
    return (player1Wins ? 1 : 0) + (player2Wins ? 2 : 0);
}

入力と出力に構造化タイプを使用する方が良いでしょう。入力には実際には2つのフィールドがあります。位置とタイプ(ブロックまたは攻撃)です。出力には、player1Winsとplayer2Winsの2つのフィールドもあります。これを単一の整数にエンコードすると、コードが読みにくくなります。

class PlayerMove {
    PlayerMovePosition pos;
    PlayerMoveType type;
}

enum PlayerMovePosition {
    HIGH,LOW
}

enum PlayerMoveType {
    BLOCK,ATTACK
}

class AttackResult {
    boolean player1Wins;
    boolean player2Wins;

    public AttackResult(boolean player1Wins, boolean player2Wins) {
        this.player1Wins = player1Wins;
        this.player2Wins = player2Wins;
    }
}

AttackResult fightMath(PlayerMove a, PlayerMove b) {
    return new AttackResult(isWinningMove(a, b), isWinningMove(b, a));
}

boolean isWinningMove(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.ATTACK && !successfulBlock(b, a)
            || successfulBlock(a, b);
}

boolean successfulBlock(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.BLOCK 
            && b.type == PlayerMoveType.ATTACK 
            && a.pos == b.pos;
}

残念ながら、Javaはこれらの種類のデータ型を表現するのがあまり得意ではありません。


-2

代わりに、このようなことをしてください

   public int fightMath(int one, int two) {
    return Calculate(one,two)

    }


    private int Calculate(int one,int two){

    if (one==0){
        if(two==0){
     //return value}
    }else if (one==1){
   // return value as per condtiion
    }

    }

4
パブリック関数でラップされたプライベート関数を作成しました。なぜこれをpublic関数に実装しないのですか?
Martin Ueding 2014年

5
また、ifステートメントの数を減らしていません。
チャド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.