ChatGPT解决这个技术问题 Extra ChatGPT

'if'语句太多?

以下代码确实可以按我的需要工作,但它很丑陋、过度或其他一些事情。我查看了公式并尝试编写一些解决方案,但我最终得到了类似数量的语句。

在这种情况下,是否有一种数学公式对我有利,或者如果陈述可以接受,是否有 16 个?

为了解释代码,这是一种基于同时回合的游戏。两个玩家每个有四个动作按钮,结果来自一个数组(0-3),但变量“一”和“二”可以是如果这有帮助,分配任何东西。结果是,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;
}
这里肯定有一些可以概括而不是蛮力的逻辑吗?在一般情况下肯定有一些函数 f(a, b) 可以产生答案吗?您还没有解释计算的逻辑,因此所有答案都只是猪的口红。我将首先认真重新考虑您的程序逻辑,使用 int 标志进行操作非常过时。 enum 可以包含逻辑并且是描述性的,这将允许您以更现代的方式编写代码。
在阅读了上面链接的替代问题中提供的@Steve Benett 答案后,我可以假设没有直接的公式答案,因为它与数据库基本相同。我试图在最初的问题中解释我正在制作一个简单的游戏(战斗机),用户可以选择 4 个按钮:blockHigh(0)、blockLow(1)、attackHigh(2) 和 attackLow(3)。这些数字保存在一个数组中,直到需要为止。稍后它们被函数 'fightMath()' 使用,该函数调用 playerOne 对 playerTwos 的选择以给出结果。没有实际的碰撞检测。
如果您有答案,请照此发布。评论中的扩展讨论很难理解,尤其是在涉及代码时。如果您想讨论这个问题是否应该被迁移到 Code Review,有一个 Meta 讨论。
“与数据库相同”是什么意思?如果这些值在数据库中,请从那里提取它们。否则,如果它真的这么复杂,我会像你拥有它一样保留它,并在每一行之后添加业务逻辑注释,以便人们了解发生了什么。 (对我来说)长而明确的更好——将来的某个人可以理解正在发生的事情。如果你把它放在一张地图中或者试着节省 8 行代码,好处真的很小,而缩减的空间更大:你让那些有一天需要阅读你的代码的人越来越困惑。

J
Jojodmo

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

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

这很有趣,因为我以前没有见过这个解决方案。我不太确定我是否理解返回结果,但会喜欢测试它。
您不需要断言,如果一个或多个索引超出范围,Java 无论如何都会抛出 IndexOutOfBoundsException
@JoeHarper如果您想要一些易于阅读的内容,那么您首先不会使用幻数,并且您将获得解释映射的评论。事实上,我更喜欢这个版本而不是原来的版本,但是对于长期可维护的东西,我会使用涉及枚举类型或至少命名常量的方法。
@JoeHarper“理论上”是一回事,“实际上”是另一回事。当然,我尝试使用描述性命名(循环变量的 i/j/k 约定除外)、命名常量、以可读方式排列我的代码等,但是当变量和函数名称开始占用超过 20 个字符,我发现它实际上会导致代码的可读性降低。我通常的方法是尝试使用易于理解但简洁的代码,并在此处和那里添加注释来解释为什么代码的结构方式(与如何)。将原因放在名称中只会使一切变得混乱。
我喜欢这个特定问题的解决方案,因为结果实际上是由结果矩阵决定的。
w
waTeim

由于您的数据集很小,您可以将所有内容压缩为 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 是数字 1,2 是数字 2,N = 4*one +两个; N = {0,1,2,...,15} - 十六个不同的值,这很重要。该函数的输出是一个 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。表达式 (one << 3) 和 (two << 1) 只是将事物相乘,同时注意到 2*x = x << 1 和 8*x = x << 3。


聪明,但没有其他人阅读代码将有机会理解它。
我认为这是非常糟糕的做法。除了作者之外,没有其他人会理解这一点。您想查看一段代码并快速理解它。但这只是浪费时间。
我和@BalázsMáriaNémeth 在这方面。虽然令人印象深刻,但您应该为暴力精神病患者编码!
所有反对者都认为这是一种可怕的代码气味。所有支持者的想法都一样,但钦佩其背后的聪明才智。 +1(永远不要使用此代码。)
多么可爱的 write only code 示例!
E
Eric Lippert

我不喜欢除 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);
}    

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

然而,这可能会更好;该布尔数组有些不透明。我喜欢表格查找方法,但我更倾向于以明确游戏语义的方式编写它。即与其说“一攻零一防不命中”,而是想办法让代码更清楚地暗示“低踢攻击低格挡不命中”。让代码体现游戏的业务逻辑。


废话。大多数经验丰富的程序将能够欣赏这里给出的建议,并将编码风格应用于他们自己的语言。问题是如何避免一串如果。这显示了如何。
@user3414693:我很清楚这是一个 Java 问题。如果您仔细阅读答案,就会变得清楚。如果您认为我的回答不明智,那么我鼓励您编写自己更喜欢的答案。
@EricLippert 我也喜欢 JAB 的解决方案。恕我直言,C# 中的枚举类型还有很多不足之处。它没有遵循其他功能所遵循的成功哲学的陷阱。例如stackoverflow.com/a/847353/92414 c# 团队是否有计划创建一个设计更好的新枚举类型(以避免破坏现有代码)?
@SolutionYogi:我也不太喜欢 C# 中的枚举,尽管出于良好的历史原因,它们是这样的。 (主要是为了与现有的 COM 枚举兼容。)我不知道有任何计划在 C# 6 中为枚举添加新设备。
@SList 不,评论不会运行。 OP 确实做了应该做的事情;将注释转换为清晰的代码。参见例如 Steve McConnell 代码完成 stevemcconnell.com/cccntnt.htm
d
djm.im

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

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

J
JAB

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

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,尽管使用 EnumSet 可能会比直接相等检查稍慢.

对于成功阻止导致计数器的情况,您可以将 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;
}

此外,使用三元运算符 (boolean_expression ? result_if_true : result_if_false) 替换某些 if 语句可以使代码更紧凑(例如,前面块中的代码将变为 return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;),但这可能会导致更难-to-read oneliners 所以我不推荐它用于更复杂的分支。


我肯定会对此进行研究,但我当前的代码允许我使用 onetwo 的 int 值作为我的 spritesheet 上的起点。尽管它不需要太多额外的代码来实现这一点。
@TomFirth84 有一个 EnumMap 类可用于将枚举映射到整数偏移量(您也可以直接使用枚举成员的序数值,例如 Move.ATTACK_HIGH.ordinal() 将是 0Move.ATTACK_LOW.ordinal() 将是 { 6} 等,但是这比显式地将每个成员与一个值关联起来更脆弱/更不灵活,因为在现有成员之间添加枚举值会抛出计数,而 EnumMap 不会出现这种情况。)
这是最易读的解决方案,因为它将代码翻译成对阅读代码的人有意义的东西。
您的代码,至少是使用枚举的代码是错误的。根据 OP 中的 if 语句,一个成功的块会导致攻击者受到攻击。但是对于有意义的代码 +1。
您甚至可以将 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; }),并在需要时在特定动作上覆盖它(任何块都可以阻挡的弱动作,永远不会适得其反的攻击等)
P
Penguin9

为什么不使用数组?

我将从头开始。我看到了一个模式,值从 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

当我们将其转换回十进制值时,我们会看到一个非常可能的值数组,其中 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 的位移几乎肯定比 4 的乘法快。乘以 4 最多可以识别 4 是 2^2 并自己进行位移(可能由编译器翻译)。坦率地说,对我来说,这种转变更具可读性。
我喜欢你的方法!它本质上将一个 4x4 矩阵扁平化为一个 16 元素数组。
现在,如果我没记错的话,编译器无疑会识别出你正在乘以 2 的幂并相应地对其进行优化。所以对你,程序员来说,位移和乘法应该有完全相同的性能。
e
elias

这使用了一点位魔法(您已经通过在一个整数中保存两位信息(低/高和攻击/块)来做到这一点):

我没有运行它,只是在这里输入,请仔细检查。这个想法肯定行得通。编辑:现在对每个输入都进行了测试,工作正常。

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

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


我同意这个解决方案,看起来很像我在对主要问题的评论中所想的。我更愿意将其拆分为单独的变量,这样可以更容易地在将来添加中间攻击。
我只是在测试后修复了上面代码中的一些错误,现在它运行良好。进一步研究位操纵器的方式,我还提出了一个单行解决方案,它仍然不像其他答案中的位掩码那样神秘,但仍然很棘手,足以让你大吃一惊:return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
这是最好的答案,因为任何阅读它的新程序员都会真正理解所有这些神奇数字背后发生的魔力。
J
Joe Harper

老实说,每个人都有自己的代码风格。我不会认为性能会受到太大影响。如果您比使用 switch case 版本更了解这一点,请继续使用它。

您可以嵌套 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; }
}

只需按照您的喜好重新格式化即可。

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


不知道这是否真的很好,但对于这种情况,我可能会使用嵌套的 switch 语句。这将占用更多空间,但它会非常清楚。
这也可以,但我想这是一个偏好问题。我实际上更喜欢 if 语句,因为它实际上是在说明代码在做什么。当然,不要放弃你的意见,不管对你有用:)。支持替代建议!
J
Jack Aidley

让我们看看我们知道什么

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 对抗每一个,那么两个都击中

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

// 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 显然意味着什么;它们不是任意值,因此命名它们会有所帮助。


C
Chris

我希望我能正确理解逻辑。怎么样:

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

找到模式的好方法!
我喜欢这种方法,但恐怕这个解决方案完全没有通过阻止攻击来命中的可能性(例如,如果一个=0 和两个=2,它返回 0,但根据规范预期为 1)。也许你可以努力让它正确,但我不确定生成的代码是否仍然如此优雅,因为这意味着行会变得更长一些。
没有意识到“命中”被授予阻止。感谢您指出。通过非常简单的修复进行了调整。
F
Francisco Presencia

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

我会用一个简单的开关。为此,您需要一个数字评估。但是,对于这种情况,由于 0 <= one < 4 <= 90 <= two < 4 <= 9,我们可以通过将 one 乘以 10 并添加 two 将两个 int 转换为简单的 int。然后在结果数字中使用一个开关,如下所示:

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
这个。对于这么少的输入,我会使用一个带有复合值的开关(尽管它可能使用大于 10 的乘数,例如 100 或 1000 更易读)。
N
Nick Dandoulakis

由于您更喜欢嵌套的 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 我本可以使用 else 链,但没有任何区别。
我知道这很简单,但是添加 else 链不会执行得更快吗? 4 例中有 3 例?我总是养成编写代码以尽可能快地执行的习惯,即使它只有几个周期。
@BrandonBearden 在这里他们不会有任何区别(假设输入始终在 0..3 范围内)。无论如何,编译器可能都会对代码进行微优化。如果我们有很长的一系列 else if 语句,我们可以使用 switch 或查找表来加速代码。
怎么会这样?如果 one==0 它将运行代码,那么它必须检查 if one==1 然后 if one==2 最后 if one==3 - 如果那里有 else if,它不会执行最后三个检查,因为它将在第一场比赛后退出该语句。是的,您可以通过使用 switch 语句代替 if (one... 语句来进一步优化,然后在 one's 的情况下进一步使用另一个 switch。然而,这不是我的问题。
C
Community

用开关外壳试试...

查看 herehere 了解更多信息

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

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

PS:只有满足一个条件..

如果同时出现2个条件..我认为不能使用switch。但是你可以在这里减少你的代码。

Java switch statement multiple cases


D
David R Tribble

我首先想到的是与弗朗西斯科·普雷森西亚(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 的哪些值对应于哪些返回值。


这是我的另一个答案 here 的变体。
D
Dawood ibn Kareem
((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2

你花了多长时间才达到这个目标?
@matchkarov 大约 10 分钟阅读其他答案,然后用铅笔和纸涂鸦 10 分钟。
如果我必须保持这一点,我会非常难过。
嗯...有一个错误:您缺少; --unicorns
我同意@Meryovi,简洁的道具,但像 APL 代码一样糟糕
C
Community

您可以使用 switch case 而不是多个 if

还要提一下,既然你有两个变量,那么你必须合并这两个变量才能在 switch 中使用它们

检查此Java switch statement to handle two variables?


A
AnonNihcas

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

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

以上将减少至少 3 个 if 语句。我没有看到一个固定的模式,也无法从给定的代码中收集到太多信息——但如果可以推导出这样的逻辑,它将减少一些 if 语句。

希望这可以帮助。


M
Marcellus

一个好点是将规则定义为文本,这样您就可以更容易地推导出正确的公式。这是从 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 命中进行一些解释会很棒,看起来很有趣!


P
P.W.

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

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

不包含任何“神奇”数字;)希望它有所帮助。


像这样的公式将无法在以后更改(更新)组合的结果。唯一的方法是重新设计整个公式。
@SNag:我同意这一点。最灵活的解决方案是使用二维数组。但是这篇文章的作者想要一个公式,这是你可以通过简单的 ifs 和数学得到的最好的公式。
K
Kirill Gamazkov

我个人喜欢级联三元运算符:

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

C
Community

这是一个相当简洁的版本,类似于 JAB's response。这利用地图来存储哪些移动胜利超过了其他。

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 是的,好主意。我总是忘记这种类型的存在。而且......建造一个是多么尴尬!
K
Khaled.K

我会使用 Map,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);
    }
}

u
user1837841

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


T
TomFirth

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

我可能会在某个时候回到这一点,但如果多个 if 语句没有造成重大阻力,那么我将暂时保留这一点。我将进一步研究表格矩阵和 switch 语句解决方案。

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

这实际上比原来的可读性差,并且不会减少 if 语句的数量......
@Chad 这样做的想法是提高处理速度,虽然它看起来很可怕,但如果我将来添加更多操作,它很容易更新。这么说我现在正在使用以前不完全理解的先前答案。
@TomFirth84 是否有理由说明您的 if 语句没有遵循正确的编码约定?
@ylun:在将其粘贴到 SO 之前,我已经减少了行数,不是为了可读性,而是为了纯粹的垃圾邮件空间。此页面上有各种不同的练习,遗憾的是,这只是我学习和习惯的方式。
@TomFirth84 我认为这不容易更新,行数的增长类似于允许值数的乘积。
P
Peter Zeller

使用常量或枚举使代码更具可读性 尝试将代码拆分为更多函数 尝试使用问题的对称性

这是一个建议,但在这里使用 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);
}

对输入和输出使用结构化类型会更好。输入实际上有两个字段:位置和类型(阻挡或攻击)。输出也有两个字段: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 并不擅长表达这些数据类型。


o
onkar

而是做这样的事情

   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
    }

    }

您刚刚创建了一个由公共函数包装的私有函数。为什么不在公共函数中实现它呢?
而且您没有减少 if 语句的数量。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅