【问题标题】:Is there a more efficient way to write multiple if else? [duplicate]有没有更有效的方法来编写多个 if else? [复制]
【发布时间】:2019-08-15 13:25:25
【问题描述】:

我需要将平均分数与字母等级相匹配。这意味着

if (90 < avg && avg < 100) {
     return 'A';
} 

以此类推,直到 'F',带有 5 个 if-else 语句。

这是很多重复,我匹配的范围是相同的长度。

有没有更有效的方法来做到这一点?我不想重复 if-else 语句 5 次。

【问题讨论】:

  • 整数除以10,然后使用switch
  • 这个问题正在meta讨论
  • @Trilarion 成绩总是整数吗?,ifstatement 永远不会捕获 10 的倍数,这是故意的吗?,90 是“A”还是“B”?,什么大约100?那是比“A”更高的等级还是“A”? if 语句有什么问题?什么样的答案,一个复杂的高性能答案?一个容易理解的?名单还在继续……
  • @NickA SO 在这种情况下的一个技巧是让问题适应答案,不管是谁做的。如果有用的答案被忽略,那将是很遗憾的,只是因为提问者的回答不正确。

标签: java if-statement range code-duplication


【解决方案1】:

我喜欢使用enum 来解决这类问题。

您只需在枚举值中定义范围并使用谓词。

import java.util.*;

public class MyClass {
    public static void main(String args[]) {
      System.out.println(Letter.getValue(13)); // B
      System.out.println(Letter.getValue(28)); // C
      System.out.println(Letter.getValue(43)); // empty
    }
}

enum Letter {

    A(1, 10), B(11, 20), C(21, 30);

    int min;
    int max;

    Letter (int min, int max) {
        this.min = min;
        this.max = max;
    }

    boolean inRange(int v) {
        return v >= min && v <= max;
    }

    static Optional<Letter> getValue(int i) {
        return Arrays.stream(values())
           .filter(v -> v.inRange(i))
           .findAny();
    }

}

【讨论】:

    【解决方案2】:

    您可能会发现这种方法冗长且过度设计。是的,它是。

    不过,我喜欢评分系统的定义方式。

    class Test {
      public static void main(String[] args) {
        Map<String, Predicate<Integer>> gradingSystem = new HashMap<>();
    
        gradingSystem.put("A", mark -> mark >= 90 && mark <= 100);
        gradingSystem.put("B", mark -> mark >= 80 && mark < 90);
    
        translateScoreIntoGrade(86, gradingSystem); // B
        translateScoreIntoGrade(95, gradingSystem); // A
      }
    
      public static String translateScoreIntoGrade(int score, Map<String, Predicate<Integer>> gradingSystem) {
        return gradingSystem.entrySet().stream()
            .filter(gradePredicate -> gradePredicate.getValue().test(score))
            .findFirst()
            .map(Map.Entry::getKey)
            .orElseThrow(() -> new IllegalArgumentException("This grade isn't valid for this system!"));
      }
    } 
    

    【讨论】:

    • 我更喜欢90 &lt;= mark &amp;&amp; mark &lt;= 100 表示法(顺序)
    • @Michael 我认为这里的区别在于(不)使用外部库 - 就个人而言,这就是我更喜欢这个答案的原因。至于提问者,我不知道。
    • 嗯,写谓词的时候还是有一些重复
    • 使用排序映射可以避免重复,但它非常灵活,因为 规则 可以根据需要尽可能复杂(如果放在一个小类中,你可以不同的评分系统)
    • @ArnaudClaudel 在我看来,您推广的方法不够灵活(尤其是方法inRange)。如果A只有100怎么办?如果 B 从 80 到 90并且在考试中 X 的正确答案是什么?条件可能会有很大差异。
    【解决方案3】:

    你可以像这个例子那样做:

    public static String avgValuetoGrade(int value) {
            int index = value/10;
            int gradeIndex = Math.max(0, index - 4);
            return (char)('F'-gradeIndex );
        }
    

    首先将平均值除以 10,得到数字 0-9(整数除法)。接下来,您必须以某种方式将数字 0-4 减少到一个数字 -4 为您提供数字 0-5 并将 0-4 映射到 0。然后您可以返回字符(感谢 @Carlos Heuberger)。

    【讨论】:

    • 使用成绩数组并通过其在数组中的索引获取成绩比使用 switch 语句更简单。
    • 不需要switch(char)('F'-num) 可以(但没有错误检测)——而且非常神秘,但所有的数学都太复杂了
    • 可以使用Math.abs(div - 4) 代替Math.max(0, div - 4)
    • 它涵盖了 OP 的需求,但很棘手且不通用
    • 如果要求发生变化,我们想将成绩从 1 退回到 5 怎么办?您的实现和逻辑与字母 A-F 相关联
    【解决方案4】:

    这将是一个以函数式风格编写的相同内容的示例。没有重复,但是很冗长。

    我不会使用它,除非乐队的大小不同(你可以exploit integer division

    Map<Range<Integer>, Character> rangeToGrade = Map.of(
       Range.between(90, 100), 'A',
       Range.between(80, 90), 'B'
       //... the rest
    );
    
    int mark = 50;
    char grade = rangeToGrade.entrySet().stream()
        .filter(e -> e.getKey().contains(mark))
        .map(Map.Entry::getValue)
        .findFirst()
        .orElse('?'); // or throw an exception
    

    Map.of 来自 Java 9。Range 来自例如Apache Commons

    【讨论】:

    • 这比 6 if/else 更长、更难阅读、效率更低,恕我直言。而且它也需要很大的依赖。
    • 从我的角度来看,这是最好的解决方案。外部依赖(Range)可以替换为自己的简单类
    • 重点是使用重复次数少的代码,效率更高。你的代码是如何切中要害的?
    • 我当然知道。我的观点是,这与使用 if/else 的解决方案具有完全相同的重复次数,除了这些重复之外还有其他代码,效率不高,而且恕我直言,可读性较差。你可能不同意我的观点,但我不明白我是如何错过重点的。
    • @JBNizet 不,它的重复次数更少。检查分数是否在一个等级内是在一行上。我不必担心不小心写了&lt; 而不是&gt; 会破坏一切。
    【解决方案5】:

    您可以将其映射到数组并避免分支预测规则,从而提高效率并节省 if/else 内容。

    class StackOverflow
    {
        public static void main(String args[])
        {
            char grades[]={'A','B','C','D','E','F'};
            convert(grades,95);
            convert(grades,90);
            convert(grades,110);
            convert(grades,123);
            convert(grades,150);
        }
        static void convert(char grades[],int marks)
        {
            if(marks<=90||marks>=150)
            {
                System.out.println("Invalid marks");
                return;
            }
            int val=marks/10-9;
            System.out.println(grades[val]);
        }
    }
    

    注意:这里假设 91-99 是 A,100-109 是 B,110-119 是 C 等等。如果您想避免使用数字 100,110 等,只需在上面的 if 语句中添加规则 ||marks%10==0

    希望这有帮助:)

    【讨论】:

      【解决方案6】:

      注意:这是使用Java 8 制作的,无权访问MapOf,也没有使用外部库。也没有使用 Streams 来显示其他选项。

      我创建了一个GradeBook 类,当它被实例化时,Map 的类字段中填充了用于查找字母等级的键。然后,您可以从您的GradeBook 对象中调用.getGrade(int),它还将处理负输入和输入高于100 的情况,并返回N。否则它将从Map 返回正确的成绩。

      这更像是一种面向对象的方法,而不是使用static 方法调用:

      public class GradeBook {
      
          private Map<Integer, Character> map;
      
          public GradeBook() {
              constructMap();
          }
      
          public char getGrade(int grade) {
              if (grade >= 0 && grade <= 100) {
                  return map.get(Math.max(0, grade/10 - 4));
              }
              else {
                  return 'N';
              }
          }
      
          public void constructMap() {
              //You can use MapOf to add all of them at once in Java9+
              map = new HashMap<>();
              map.put(0, 'F');
              map.put(1, 'E');
              map.put(2, 'D');
              map.put(3, 'C');
              map.put(4, 'B');
              map.put(5, 'A');
              map.put(6, 'A');
          }
      
          public static void main(String [] args) {
      
              GradeBook grades = new GradeBook();
      
              //Testing different values
              System.out.println(grades.getGrade(100));
              System.out.println(grades.getGrade(75));
              System.out.println(grades.getGrade(80));
              System.out.println(grades.getGrade(91));
              System.out.println(grades.getGrade(45));
              System.out.println(grades.getGrade(2));
              System.out.println(grades.getGrade(-1));
          }
      }
      

      输出:

      A
      C
      B
      A
      F
      F
      N
      

      这种实现的缺点是第一次实现有点长,但优点是它很容易重复使用,因为您只需在需要的任何地方创建一个new GradeBook()

      【讨论】:

        【解决方案7】:

        只是为了炫耀,在 Java 版本 12(启用预览版)中,使用新的 Switch 表达式:

        String grade = switch(avg/10) {
            case 9,10 -> "A";
            case 8    -> "B";
            case 7    -> "C";
            case 6    -> "D";
            case 5    -> "E";
            default   -> "F";
        };
        

        或者,如果不偷懒的话,非常灵活:

        String grade = switch(avg) {
            case 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 -> "A";
            case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89      -> "B";
            // you got the idea, I AM lazy
        

        真正的 (?) 解决方案:使用NavigableMap(例如TreeMap)及其floorEntry()lowerEntry() 方法:

        NavigableMap<Integer, String> grades = new TreeMap<>();
        grades.put(90, "A");
        grades.put(80, "B"); 
        ...
        // usage
        String grade = grades.floorEntry(avg).getValue();
        

        地图中的值最终必须调整

        【讨论】:

        • @AndrewTobilko 同意,至少它很糟糕,但最终是最快的(只是一个跳跃表,与普通开关相比 {我相信})
        猜你喜欢
        • 2023-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-19
        • 1970-01-01
        • 1970-01-01
        • 2022-11-01
        • 2011-12-15
        相关资源
        最近更新 更多