关于数学:太多’如果’陈述?

Too many 'if' statements?

下面的代码确实按我的需要工作,但是它很难看,太多或者其他一些东西。我已经研究过公式,并试图写出一些解决方案,但最终得到的语句量也差不多。

在这种情况下,是否有一种数学公式可以让我受益?如果语句可以接受,它是16?

为了解释代码,这是一种同时基于回合的游戏。两个玩家各有四个操作按钮,结果来自一个数组(0-3),但如果这有帮助,变量"1"和"2"可以分配任何内容。结果是,0=双赢,1=p1赢,2=p2赢,3=双赢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}


如果你不能想出一个公式,你可以用一个表格来计算有限数量的结果:

1
2
3
4
5
6
7
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];


由于数据集太小,可以将所有数据压缩为1个长整数,然后将其转换为公式

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

更多位变量:

这就利用了这样一个事实:一切都是2的倍数

1
2
3
4
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_的映射。每个答案都涉及到了如何实现该映射。

另外,在许多答案中,你可以看到一个问题的重述,即一个2位数的基4位数n(1,2),其中1是位数1,2是位数2,n=4*1+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。


我不喜欢除了jab之外的任何解决方案。其他任何解决方案都不能使阅读代码和理解正在计算的内容变得容易。

下面是我如何编写这个代码——我只知道C,而不是Java,但是你可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}

现在更清楚的是这里计算的是什么:这强调了我们正在计算受到什么攻击的人,并返回两个结果。

然而,这可能会更好;布尔数组有些不透明。我喜欢表查找方法,但是我倾向于以这样一种方式来编写它,使它清楚地知道预期的游戏语义是什么。也就是说,与其说"零攻击,一次防御不命中",不如说找到一种方法让代码更清楚地意味着"低踢攻击和低块防御不命中"。使代码反映游戏的业务逻辑。


您可以创建包含结果的矩阵

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

当你想要得到价值的时候,你会用到

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


其他人已经提出了我最初的想法,矩阵方法,但是除了合并if语句之外,通过确保提供的参数在预期范围内并使用就地返回(我见过的一些编码标准强制函数的一个退出点,但是我发现mu对于避免箭头编码非常有用的是,在Java中存在异常的情况下,严格执行这样的规则并没有多大意义,因为在方法中抛出的任何异常都可能是退出的可能点。嵌套switch语句是一种可能,但是对于您在这里检查的小范围值,我发现语句是否更紧凑并且不太可能导致性能差异,特别是如果您的程序是基于turn的而不是实时的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}

这最终会导致可读性低于其他情况,这可能是由于输入->结果映射部分的不规则性造成的。我更喜欢矩阵样式,因为它很简单,而且您可以通过设置矩阵使其在视觉上有意义(尽管这在一定程度上受我对卡诺地图的记忆的影响):

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

更新:考虑到您提到的阻塞/命中,这里对使用属性/属性保持枚举类型的输入和结果的函数进行了更彻底的更改,并稍微修改结果以说明阻塞,这将导致更可读的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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 attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);,然后使用attacks.contains(move),而不是move.type == MoveType.ATTACK)相比,EnumSets可能更具可扩展性,尽管使用EnumSets可能比直接相等检查稍慢。

对于成功的块导致计数器的情况,您可以用

1
2
3
4
5
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;
}

此外,用三元运算符(boolean_expression ? result_if_true : result_if_false替换一些if语句可能会使代码更紧凑(例如,前面块中的代码将成为return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;代码),但这会导致更难读取一行程序,因此我不建议它进行更复杂的分支。


为什么不使用数组?

我将从头开始。我看到一个模式,值从0到3,你想要捕捉所有可能的值。这是您的桌子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

当我们查看这个相同的二进制表时,会看到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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组合成一个4位整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

当我们将其转换回十进制值时,我们会看到一个非常可能的值数组,其中一个和两个组合可以用作索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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语句,然后把它写下来,就像这样:

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

我不知道2的位移是否比乘法快。但值得一试。


这使用了一点位魔术(您已经通过在一个整数中保存两位信息(低/高&攻击/阻止)来实现了这一点):

我还没有运行它,只在这里键入了它,请双击。这个想法确实有效。编辑:现在对每个输入进行测试,工作正常。

1
2
3
4
5
6
7
8
9
10
11
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;
    }
}

或者我应该建议将这两个信息位分离成单独的变量吗?基于上面这样的位操作的代码通常很难维护。


说实话,每个人都有自己的代码风格。我不认为表演会受到太大的影响。如果您比使用交换盒版本更好地理解这一点,那么继续使用它。

您可以嵌套IFS,因此可能会对您的最后一个if检查稍微提高性能,因为它不会经历那么多if语句。但在Java基础课程的背景下,它可能不会受益。

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

所以,不是……

1
2
3
4
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; }

你会…

1
2
3
4
5
6
7
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; }
}

只需重新格式化即可。

这并不能使代码看起来更好,但我相信它可能会加快一点速度。


让我们看看我们知道什么

1:你的答案对P1(第一名)和P2(第二名)是对称的。这对一个战斗游戏来说是有意义的,但同时也是一个你可以利用的东西来改进你的逻辑。

2:3拍0拍2拍1拍3。这些案例中唯一没有涉及的案例是0对1和2对3的组合。换句话说,独特的胜利表是这样的:0比2,1比3,2比1,3比0。

3:如果0/1比对方高,那么就有一场无球平局,但是如果2/3比对方高,那么双方都会命中

首先,让我们构建一个单向函数,告诉我们是否赢了:

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

然后,我们可以使用此函数组合最终结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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显然意味着什么;它们不是任意值,因此命名它们会有所帮助。


我希望我能正确理解逻辑。比如:

1
2
3
4
5
6
7
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;
}

检查一个命中率高或一个命中率低没有被阻止,对玩家2也是如此。

编辑:算法没有完全理解,阻塞时"命中"我没有意识到(thx elias):

1
2
3
4
5
6
7
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;
}


我没有Java的经验,所以可能会有一些拼写错误。请将代码视为伪代码。

我只要一个简单的开关就行了。为此,你需要一个单一的数字评估。但是,在这种情况下,由于0 <= one < 4 <= 90 <= two < 4 <= 9,我们可以通过将one乘以10,再加上two,将两个int都转换为简单int。然后在生成的数字中使用如下开关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}

还有一个简短的方法,我只是想指出作为一个理论代码。但是我不会使用它,因为它有一些您通常不想处理的额外复杂性。额外的复杂性来自基数4,因为计数是0、1、2、3、10、11、12、13、20……

1
2
3
4
5
6
7
8
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中我会这样做:

1
2
3
4
5
6
7
8
9
10
11
12
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];
}


既然您更喜欢嵌套的if条件,下面是另一种方法。注意,它不使用result成员,也不改变任何状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}


试试开关外壳…

在这里或这里了解更多信息

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression)
{
  case constant:
        statements;
        break;
  [ case constant-2:
        statements;
        break;  ] ...
  [ default:
        statements;
        break;  ] ...
}

您可以向它添加多个条件(而不是同时添加),甚至在没有满足其他情况的情况下也可以使用默认选项。

附言:只有满足一个条件。

如果同时出现两种情况。我认为开关不能用。但是你可以在这里减少你的代码。

Java交换机语句的多个实例


我想到的第一件事基本上是弗朗西斯科·普兰西娅给出的相同的答案,但优化了一些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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个)成为默认案例来进一步优化它:

1
2
3
4
5
6
    //case 22:
    //case 23:
    //case 32:
    //case 33:
    default:
        return 3;

这种方法的优点是,与其他一些建议的方法相比,更容易看到onetwo的值对应于哪个返回值。


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


您可以使用开关盒而不是多个if

还要提到,由于有两个变量,所以必须合并这两个变量,以便在开关中使用它们。

检查这个JAVA交换机语句来处理两个变量吗?


一个好的方法是将规则定义为文本,然后可以更容易地导出正确的公式。这是从Laalto的nice数组表示中提取的:

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

这里我们将给出一些一般性的评论,但是您应该用规则术语描述它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
    }
}

当然,您可以将其简化为更少的代码,但通常最好理解您编写的代码,而不是找到一个紧凑的解决方案。

1
2
3
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点击的一些解释会很好,看起来很有趣!


当我在1/2和结果之间绘制一张表时,我看到了一个模式,

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

以上将至少减少3个if语句。我看不到一个集合模式,也不能从给定的代码中收集到很多信息——但是如果可以派生出这样的逻辑,它将减少一些if语句。

希望这有帮助。


最短且仍然可读的解决方案:

1
2
3
4
5
6
7
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;
}

或者更短:

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

不包含任何"魔力"数字;)希望它有帮助。


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


我会用一张地图,要么是哈希图要么是树映射

尤其是参数不在0 <= X < N形式时。

就像一组随机的正整数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
2
3
4
5
6
7
int result = condition1
    ? result1
    : condition2
    ? result2
    : condition3
    ? result3
    : resultElse;

但在您的情况下,您可以使用:

1
2
3
4
5
6
7
8
9
10
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];
}

或者,您可以注意到以位为单位的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

所以你可以使用魔法:

1
2
3
4
5
6
7
8
9
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);
}

感谢@joe harper,因为我最终使用了他答案的变体。为了进一步缩小它,因为每4个结果中有2个是相同的,我进一步缩小了它。

我可能会在某个时候回到这个问题上,但是如果多个if语句没有引起大的阻力,那么我现在就保留这个问题。我将进一步研究表矩阵和切换语句解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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; }
  }
}


这里有一个相当简洁的版本,类似于Jab的回答。这就利用了一张地图来存储战胜他人的行动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
  }
}

例子:

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

印刷品:

1
P1Win


  • 使用常量或枚举使代码更具可读性
  • 尝试将代码拆分为更多函数
  • 尝试使用问题的对称性
  • 这里有一个建议,它看起来是怎样的,但是在这里使用ints仍然有点难看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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);
    }

    对于输入和输出使用结构化类型会更好。输入实际上有两个字段:位置和类型(阻止或攻击)。输出也有两个字段:player1wins和player2wins。将其编码为单个整数会使代码更难读取。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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并不擅长表达这些类型的数据类型。


    而是这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
       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
        }

        }