【问题标题】:Too many 'if' statements?'if' 语句太多?
【发布时间】:2014-04-25 10:11:21
【问题描述】:

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

在这种情况下,是否有一种数学公式对我有利,或者如果语句可以接受,是否有 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;
}

【问题讨论】:

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

标签: if-statement math formula


【解决方案1】:

switch 外壳试试。..

查看herehere 了解更多信息

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

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

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

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

Java switch statement multiple cases

【讨论】:

    【解决方案2】:

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

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

    检查这个Java switch statement to handle two variables?

    【讨论】:

      【解决方案3】:

      改为这样做

         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 语句的数量。
      【解决方案4】:

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

      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 个字符,我发现它实际上会导致代码的可读性降低。我通常的方法是尝试使用 cmets 在这里和那里获得易于理解但简洁的代码,以解释 为什么 代码的结构方式(相对于方式)。将原因放在名称中只会使一切变得混乱。
      • 我喜欢这个特定问题的解决方案,因为结果实际上是由结果矩阵决定的。
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

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

        希望这会有所帮助。

        【讨论】:

          【解决方案7】:

          说实话,每个人都有自己的代码风格。我不会认为性能会受到太大影响。如果您比使用 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 语句,因为它实际上是在说明代码在做什么。当然,不要放弃你的意见,不管对你有用:)。支持替代建议!
          【解决方案8】:

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

          我可能会在某个时候回到这一点,但如果多个if-statements 没有造成重大阻力,那么我会暂时保留这个。我将进一步研究表格矩阵和 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 我认为这很容易更新,行数增长类似于允许值数的乘积。
          【解决方案9】:

          其他人已经提出了我最初的想法,即矩阵方法,但除了合并 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&lt;Move&gt; 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;
          }
          

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

          【讨论】:

          • 我肯定会研究这个,但我当前的代码允许我使用 onetwo 的 int 值作为我的 spritesheet 上的起点。虽然它不需要太多额外的代码来实现这一点。
          • @TomFirth84 有一个EnumMap 类,您可以使用它来将枚举映射到您的整数偏移量(您也可以直接使用枚举成员的序数值,例如Move.ATTACK_HIGH.ordinal() 将是@987654340 @、Move.ATTACK_LOW.ordinal() 将是 1 等,但这比显式地将每个成员与一个值关联起来更脆弱/更不灵活,因为在现有成员之间添加枚举值会抛出计数,但情况并非如此EnumMap.)
          • 这是最易读的解决方案,因为它将代码翻译成对阅读代码的人有意义的东西。
          • 您的代码,至少是使用枚举的代码,是错误的。根据 OP 中的 if 语句,一个成功的块会导致攻击者受到攻击。但是对于有意义的代码 +1。
          • 你甚至可以在Move枚举中添加一个attack(against)方法,当移动成功时返回HIT,当移动被阻挡时返回BACKFIRE,当它不是攻击时返回NOTHING .这样你就可以在一般情况下实现它(public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; }),并在需要时在特定动作上覆盖它(任何方块都可以阻挡的弱动作,永远不会适得其反的攻击等)
          【解决方案10】:

          为什么不使用数组?

          我将从头开始。我看到了一个模式,值从 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 的幂并相应地对其进行优化。所以对你,程序员来说,位移和乘法应该有完全相同的性能。
          【解决方案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;
              }
          }
          

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

          【讨论】:

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

          我不喜欢除 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/92414c#团队是否有计划创建一个设计更好的新枚举类型(以避免破坏现有代码)?
          • @SolutionYogi:我也不太喜欢 C# 中的枚举,尽管出于良好的历史原因,它们是这样的。 (主要是为了与现有的 COM 枚举兼容。)我不知道有任何计划在 C# 6 中为枚举添加新设备。
          • @SList 不,cmets 不运行。 OP 确实做了应该做的事情;将 cmets 转换为清除代码。参见例如Steve McConnell 代码完成 stevemcconnell.com/cccntnt.htm
          【解决方案13】:

          由于您的数据集很小,您可以将所有内容压缩为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*一+二; N = {0,1,2,...,15} - 十六个不同的值,这很重要。该函数的输出是一个以 4 为基数的 1 位数字 {0,1,2,3} - 4 个不同的值,也很重要。

          现在,一个以 4 为底的 1 位数字可以表示为以 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

          【讨论】:

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

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

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

          我只想指出另一种简短的方法,作为理论代码。但是我不会使用它,因为它有一些您通常不想处理的额外复杂性。额外的复杂度来自 base 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,它可能更具可读性)。
          【解决方案15】:

          由于您更喜欢嵌套的 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 它将运行代码,那么它必须检查是否one==1 然后如果one==2 最后如果one==3 - 如果有其他如果在那里,它不会做最后三个检查,因为它将在第一场比赛后退出该语句。是的,您可以通过使用 switch 语句代替 if (one... 语句来进一步优化,然后在 one's 的情况下进一步使用另一个 switch。不过,这不是我的问题。
          【解决方案16】:

          我希望我能正确理解逻辑。像这样的东西怎么样:

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

          【讨论】:

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

          让我们看看我们知道什么

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

          【讨论】:

            【解决方案18】:

            我想到的第一件事与弗朗西斯科·普雷森西亚给出的答案基本相同,但做了一些优化:

            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 的变体。
            【解决方案19】:

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

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

            【讨论】:

              【解决方案20】:

              最好将规则定义为文本,这样您就可以更轻松地推导出正确的公式。这是从 laalto 的漂亮数组表示中提取的:

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

              这里我们使用一些通用的 cmets,但你应该用规则术语来描述它们:

              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 命中的一些解释会很棒,看起来很有趣!

              【讨论】:

                【解决方案21】:
                ((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2
                

                【讨论】:

                • 您花了多长时间才达到这个目标?
                • @mbatchkarov 大约 10 分钟阅读其他答案,然后用铅笔和纸涂鸦 10 分钟。
                • 如果我不得不维持这个,我会非常难过。
                • uhmmm...有个bug:你少了; --unicorns
                • 我同意@Meryovi,简洁的道具,但像 APL 代码一样糟糕
                【解决方案22】:

                这是一个相当简洁的版本,类似于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&lt;Move, List&lt;Move&gt;&gt; beats = new java.util.EnumMap&lt;&gt;();,它应该更有效。
                • @JAB 是的,好主意。我总是忘记这种类型的存在。还有......建造一个是多么尴尬!
                【解决方案23】:

                我会使用 Map,HashMap 或 TreeMap

                特别是如果参数不在0 &lt;= X &lt; 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);
                    }
                }
                

                【讨论】:

                  【解决方案24】:

                  最短且易读的解决方案:

                  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:我同意这一点。最灵活的解决方案是使用二维数组。但是这篇文章的作者想要一个公式,这是你可以通过简单的 if 和数学得到的最好的公式。
                  【解决方案25】:
                  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 并不擅长表达这些数据类型。

                  【讨论】:

                    【解决方案26】:

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-11-29
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多