“ if”语句过多?


263

以下代码确实可以满足我的需要,但是它很丑陋,过多或其他许多事情。我已经看过公式并尝试编写一些解决方案,但最终得到了类似数量的语句。

在这种情况下,是否有一种对我有利的数学公式?如果可接受的话,公式是否为16?

为了说明代码,它是针对一种基于同时回合的游戏。.两个玩家每个都有四个动作按钮,结果来自数组(0-3),但是变量“ one”和“ two”可以是如果有帮助,则分配任何东西。结果是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可以包含逻辑并且具有描述性,这将使您能够以更现代的方式编写代码。
蜘蛛鲍里斯(Boris)

阅读了上面链接的他的替代问题中提供的@Steve Benett的答案后,我可以假定对此没有直接的公式答案,因为它与数据库基本相同。我试图在最初的问题中解释我正在制作一个简单的游戏(战斗机),并且用户可以选择4个按钮:blockHigh(0),blockLow(1),attackHigh(2)和AttackLow(3)。这些数字以数组形式保存,直到需要为止。稍后,它们由函数'fightMath()'使用,该函数针对playerTwos调用playerOne的选择以给出结果。没有实际的碰撞检测。
TomFirth 2014年

9
如果您有答案,请原样发布。注释中的扩展讨论很难进行,尤其是涉及代码时。如果您想讨论是否应该将这个问题迁移到Code Review,那么这里有一个关于Meta的讨论。
乔治·斯托克2014年

1
“与数据库相同”是什么意思?如果这些值在数据库中,请从那里拉出它们。否则,如果确实如此复杂,我将保留它的位置,并在每行之后添加业务逻辑注释,以使人们了解发生了什么。(对我而言)长期而明确地更好-将来有人可以理解正在发生的事情。如果将其放在地图中或尝试保存8行代码,则上行空间确实很小,而缩减空间却更大:对于需要一天阅读代码的人来说,它变得越来越混乱。
skaz 2014年

Answers:


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如果一个或多个索引超出范围,Java仍将抛出异常。
JAB

43
@JoeHarper如果您想轻松阅读一些内容,那么您将不会首先使用幻数,并且会有一条注释来解释该映射。实际上,我更喜欢此版本,而不是原始版本,但从长远来看,我会使用一种涉及枚举类型或至少命名常量的方法。
JAB 2014年

13
@JoeHarper“理论上”是一回事,“实践上”是另一回事。当然,我尝试使用描述性的命名(与例外i/ j/ k约定循环变量),命名常量,安排我的代码以可读的方式等等,但是当变量和函数名开始超过20个字符占用我发现它每次都会导致可读性降低。我通常的方法是尝试在此处并在那里带有注释的可理解但简洁的代码,以解释为什么代码按其方式(与方式)结构化。在名称中加上“为什么”只会使一切变得混乱。
JAB

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个数字(一个,两个),域{0,1,2,3}到范围{0,1,2,3}的映射。每个答案都与如何实现该地图有关。

此外,您还可以在许多答案中看到问题的重述,即映射为1个2位以4为底的数字N(one,two),其中一个是数字1,两个是数字2,而N = 4 * one +两个;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。表达式(一个<< 3)和(两个<< 1)只是相乘,同时注意2 * x = x << 1和8 * x = x << 3。


79
聪明,但是没有其他人可以阅读代码。
2014年

106
我认为这是极其糟糕的做法。除了作者之外,没有其他人会明白这一点。您想看一段代码并快速理解它。但这只是浪费时间。
巴拉兹内梅特

14
我对此与@BalázsMáriaNémeth保持联系。尽管令人印象深刻,但您应该为暴力精神病患者编码!
OrhanC14年

90
所有拒绝投票的人都认为这是一种可怕的代码味道。所有支持者都认为相同,但敬佩其背后的聪明才智。+1(永远不要使用此代码。)
usr

4
只写代码的一个好例子!
lealand

98

除了JAB的解决方案外,我不喜欢其他任何解决方案。 没有其他方法使阅读代码和理解正在计算的内容变得容易

这是我编写此代码的方式-我只知道C#,不知道Java,但是您会看到图片:

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);
}    

现在,我们可以清楚地知道正在计算什么:这强调我们正在计算谁受到了什么攻击,并返回了两个结果。

但是,这可能会更好。布尔数组有点不透明。我喜欢表查找方法,但是我倾向于以一种明确的意图来编写它。就是说,与其说“零攻击而防御不击中”,不如说是找到一种方法使代码更清楚地暗示“低踢攻击和低挡防御不会击中”。 使代码反映游戏的业务逻辑。


66
废话。最有经验的程序将能够欣赏这里给出的建议,并将编码样式应用于自己的语言。问题是如何避免一串ifs。这说明了如何。
GreenAsJade 2014年

6
@ user3414693:我很清楚这是一个Java问题。如果您仔细阅读了答案,这将变得很清楚。如果您认为我的答案是不明智的,那么我鼓励您编写自己喜欢的答案。
埃里克·利珀特

1
@EricLippert我也喜欢JAB的解决方案。恕我直言,C#中的枚举类型还有很多不足之处。它没有遵循其余功能所具有的成功哲学的底蕴。例如stackoverflow.com/a/847353/92414 c#团队是否有计划创建设计更好的新枚举类型(以避免破坏现有代码)?
解决方案

@SolutionYogi:我也不太喜欢C#中的枚举,尽管出于良好的历史原因,它们是这样的。(多为与现有的COM枚举兼容),我不知道有任何计划在C#6.添加新设备的枚举
埃里克利珀

3
@SList不,评论不运行。OP确实做了应做的事情;将注释转换为清晰代码。参见例如史蒂夫·麦康奈尔代码完整 stevemcconnell.com/cccntnt.htm
djechlin 2014年

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语句外,您还可以通过确保提供的参数在预期范围内并使用就地返回(某些编码,可以避免使用某些方法)我见过的标准对函数强制执行一点退出,但是我发现多次返回对于避免箭头编码非常有用,并且由于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
}

最终,由于输入->结果映射的某些部分的不规则性,它最终的可读性比其他方式差。我更喜欢矩阵样式,因为它简单易用,以及如何将矩阵设置为在视觉上有意义(尽管这在一定程度上受我对卡诺地图的记忆的影响):

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;
}

如果要添加更多高度的块/攻击,您甚至不需要更改函数本身,只需添加枚举即可;但是,添加其他类型的动作可能需要修改功能。同样,EnumSets可能比使用额外的枚举作为主要枚举的属性具有更大的可扩展性,例如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;),但这会导致难以理解的oneliner,所以我不会建议将其用于更复杂的分支。


我一定会研究这个问题,但是我当前的代码允许我使用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; })中实施它,并在需要时在特定移动上覆盖它(任何块都可以阻挡的微弱移动,永不过时的攻击等)
2014年

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合并为一个值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

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

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},数组为,其索引只是一个和两个的组合。

我不是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并自己进行移位(可能由编译器翻译)。坦白说,对我来说,这种转变更具可读性。
Cruncher 2014年

我喜欢你的方法!它实质上将4x4矩阵展平为16个元素的数组。
卡梅隆·廷克

6
如今,如果我没记错的话,编译器无疑会认识到您要乘以2的幂并相应地对其进行优化。因此对于您来说,程序员,位移和乘法应该具有完全相同的性能。
Tanner Swett 2014年

24

这使用了一点点魔术(您已经通过将两个信息位(低/高和攻击/块)保存在一个整数中来完成此操作):

我还没有运行它,只在这里输入了它,请仔细检查。这个想法肯定行得通。 编辑:现在已经对每个输入进行了测试,效果很好。

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
我同意这种解决方案,看起来很像我在评论主要问题时所想到的。我希望将其拆分为单独的变量,例如,将来可以更轻松地添加中间攻击。
2014年

2
在测试之后,我只是修复了上面代码中的一些错误,现在它可以正常工作。进一步讲解位操纵器的方式,我还提出了一个单行解决方案,该解决方案仍然不像其他答案中的位掩码那样神秘,但是仍然很棘手,使您return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
难以置信

1
这是最好的答案,因为任何新手阅读它都会真正理解所有这些魔术数字背后发生的魔术。
bezmax 2014年

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语句。这会占用更多空间,但确实很明显。
染料2014年

那也可以,但是我想这是优先考虑的问题。我实际上更喜欢if语句,因为它实际上是在说代码在做什么。当然,不要放下您的意见,不管什么对您有用:)。虽然支持替代建议!
乔·哈珀2014年

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;
}

检查一个击中高点或一个击中低点不会被阻止,第二个玩家也是如此。

编辑:算法尚未完全理解,阻止时授予我未意识到的“击中”(Thx elias):

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)。也许您可以对其进行处理以使其正确,但是我不确定所生成的代码是否仍然如此优美,因为这意味着行数会更长一些。
elias 2014年

没有意识到“成功”被授予封锁。感谢您指出。调整非常简单的修复程序。
克里斯

10

我没有Java经验,所以可能会有一些错别字。请将该代码视为伪代码。

我会用一个简单的开关。为此,您需要一个数字评估。但是,对于这种情况,由于0 <= one < 4 <= 90 <= two < 4 <= 9,我们可以通过乘以one10并加将两个整数转换为简单的整数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;
}

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

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版之前没有lamdas
Kirill Gamazkov

1
这个。对于这么少的输入,我将使用带复合值的开关(尽管乘数大于10(例如100或1000)可能更具可读性)。
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例?我总是养成编写代码以尽可能快地执行的习惯,即使这只是几个周期。
布兰登·比尔登

2
@BrandonBearden在这里,它们不会有任何区别(假设输入始终在0..3范围内)。无论如何,编译器可能会对代码进行微优化。如果我们有很长的else if语句系列,则可以使用switch或查找表来加速代码。
Nick Dandoulakis 2014年

怎么了 如果one==0它将运行代码,则将必须检查是否,one==1是否one==2以及最终是否one==3-如果在其中,如果不存在其他代码,则它将不执行最后三个检查,因为它将在第一次匹配后退出该语句。是的,您可以通过使用switch语句代替语句来进一步优化if (one...,然后在的情况下进一步使用另一个switch one's。但是,这不是我的问题。
布兰登·比尔登

6

尝试使用开关外壳。..

在这里这里看看更多信息

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

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

PS:只有满足一个条件。

如果同时出现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 2014年

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

4
您花了多长时间到达此位置?
mbatchkarov 2014年

2
@mbatchkarov大约10分钟阅读其他答案,然后10分钟用铅笔和纸书写。
达伍德·伊本·卡里姆

7
如果我必须保持这一点,我将感到非常难过。
Meryovi 2014年

嗯...有个错误:您不见了; --unicorns
Alberto

我同意@Meryovi,它是简洁却又像APL代码一样糟糕的道具
Ed Griebel 2014年


3

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

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阵列。但是这篇文章的导师想要一个公式,这是仅凭简单的if和数学就可以得到的最佳公式。
2014年

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

@JAB是的,好主意。我总是忘记这种类型的存在。而且...构造一个是多么尴尬!
Duncan Jones

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语句的数量...
2014年

@Chad这样的想法是为了提高处理速度,尽管它看起来很可怕,但是如果将来我添加更多操作,就很容易进行更新。说这句话,我现在使用的是以前的答案,而我以前并没有完全理解。
TomFirth 2014年

3
@ TomFirth84是否存在您未遵循if语句正确的编码约定的原因?
ylun.ca 2014年

@ylun:在将其粘贴到SO之前,我已经减少了行数,这不是为了可读性而是为了增加垃圾邮件空间。本页上的练习各有不同,可悲的是,这只是我所学且自如的方法。
TomFirth 2014年

2
@ TomFirth84我认为这很容易更新,行数的增长类似于允许值数量的乘积。
Andrew Lazarus 2014年

0
  1. 使用常量或枚举使代码更具可读性
  2. 尝试将代码拆分为更多功能
  3. 尝试使用问题的对称性

这是一个建议,看起来像什么,但是在这里使用整数仍然很丑陋:

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。将其编码为单个整数使读取代码更加困难。

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
您刚刚创建了一个由公共函数包装的私有函数。为什么不只是在公共功能中实现呢?
Martin Ueding 2014年

5
并且您还没有减少if语句的数量。
乍得
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.