四则运算器

钟宝骏 3123004249

这个作业属于哪个课程 班级的链接
这个作业要求在哪里 作业要求
这个作业的目标 提高工程能力和算法能力

github: https://github.com/Tsukilc/cal

1. PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 5 5
· Estimate · 估计这个任务需要多少时间 220 200
Development 开发 160 160
· Analysis · 需求分析 (包括学习新技术) 5 5
· Design Spec · 生成设计文档 5 5
· Design Review · 设计复审 (和同事审核设计文档) 5 5
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 5 5
· Coding · 具体编码 100 100
· Code Review · 代码复审 10 10
· Test · 测试(自我测试,修改代码,提交修改) 20 20
Reporting 报告 40 40
· Test Report · 测试报告 20 20
· Size Measurement · 计算工作量 5 5
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 15 15
合计 220 200

2. 效能分析

2.1 改进程序性能花费的时间

20分钟

2.2 改进思路

首先,我使用了性能分析工具,如JProfiler和VisualVM,识别了程序中的关键瓶颈。通过这些工具,我们能够明确哪些模块和操作对性能影响最大。  

之后我从字符串处理的细节优化了生成算术题的代码

并且优化了文件传输的代码

2.3 性能分析图

火焰图

方法耗时图

时间线图

2.4 程序中消耗最大的函数

是生成问题和答案的函数!因为涉及到了很多字符串处理和分数操作,并且还要给问题算出最简的分数答案,耗时比较多

3. 设计实现过程



4. 代码说明

4.1 关键代码展示

  • 生成表达式

通过控制运算符数量、操作数范围和括号的使用,能够构建出多种多样的复杂表达式。它保证了运算符和操作数之间的合理性,例如避免负数结果和除零错误,同时还根据概率随机在子表达式中添加括号,从而提升了表达式的多样性和复杂度。

这里我想到了一个方法,先把字符串的空格全部清掉,方便字符串处理

之后再全部加回去,方便阅读

    private static final int MAX_OPERATORS = 3; // 最大运算符数量
    private static final double BRACKET_PROBABILITY = 0.7; // 括号出现的概率


    // 生成表达式的方法
    public static String generateExpression(int numOperations, int rangeLimit) {
        if (numOperations > MAX_OPERATORS) {
            throw new IllegalArgumentException("运算符数量不能超过 " + MAX_OPERATORS);
        }

        StringBuilder expression = new StringBuilder();
        ArrayList<String> operators = new ArrayList<>(Arrays.asList("+", "-", "*", "/"));

        // 生成第一个操作数
        int operand = random.nextInt(rangeLimit);
        expression.append(operand);

        // 生成后续的操作数和运算符
        for (int i = 0; i < numOperations; i++) {
            // 随机选择运算符
            String operator = operators.get(random.nextInt(operators.size()));
            // 随机选择下一个操作数
            operand = random.nextInt(rangeLimit);

            // 确保减法不会导致负数结果
            if (operator.equals("-") && operand > 0) {
                operand = random.nextInt(rangeLimit - 1);
            }

            // 确保除法结果为真分数
            if (operator.equals("/")) {
                int denominator = random.nextInt(rangeLimit - 1) + 1; // 避免除以零
                expression.append("/").append(denominator);
            } else {
                // 添加运算符和操作数到表达式
                expression.append(operator).append(operand);
            }

            // 随机决定是否在当前子表达式周围添加括号
            if (random.nextDouble() < BRACKET_PROBABILITY) {
                int insertPos = random.nextInt(expression.length());

                if (insertPos + 1 >= expression.length()){
                    continue;
                }

                char nextChar = expression.charAt(insertPos + 1);
                if ( nextChar == '*' || nextChar == '/' || isDigit(nextChar))
                    continue;
                expression.insert(insertPos, "(");
                expression.append(")");
            }
        }

        // 确保括号正确匹配
        String result = expression.toString().replaceAll("\\(\\s*\\*", "\\(\\*").replaceAll("\\(\\s*/", "\\(/");

        // 给result表达式加上空格
        result = result.replaceAll("\\+", " + ").replaceAll("-", " - ").replaceAll("\\*", " * ").replaceAll("/", " / ");


        return result;
    }

    private static boolean isDigit(char c) {
        return c >= '0' && c <= '9';
    }
  • 表达式计算器
通过栈结构巧妙地进行运算符优先级管理和括号匹配,确保计算顺序正确。类中的分数以最简形式返回,且能有效处理各种复杂的表达式。  

在实现上,类提供了以下特点:

  1. 表达式有效性验证:在计算之前,首先检查表达式是否有效,确保括号匹配且没有非法字符。
  2. 栈式计算:利用栈管理分子、分母和操作符,确保操作符优先级和括号的正确处理。
  3. 分数计算:对加、减、乘、除四则运算进行精确的分数运算,避免浮点数误差。
  4. 分数简化:运算结果会自动简化为最简分数形式,确保输出结果简洁、精准。
    public static String evaluateExpression(String expr) {
        try {
            // 去掉所有空格
            expr = expr.replaceAll(" ", "");

            // 验证表达式的有效性
            if (!isValidExpression(expr)) {
                throw new IllegalArgumentException("Invalid expression");
            }

            // 使用栈来处理表达式
            Stack<BigInteger> numerators = new Stack<>();  // 分子栈
            Stack<BigInteger> denominators = new Stack<>();  // 分母栈
            Stack<Character> operators = new Stack<>();  // 操作符栈

            int i = 0;
            while (i < expr.length()) {
                char c = expr.charAt(i);

                if (isDigit(c)) {
                    // 解析数字
                    StringBuilder numBuilder = new StringBuilder();
                    while (i < expr.length() && isDigit(expr.charAt(i))) {
                        numBuilder.append(expr.charAt(i++));
                    }
                    BigInteger numerator = new BigInteger(numBuilder.toString());
                    BigInteger denominator = BigInteger.ONE;

                    // 处理分数形式:如果有 / 处理分母
                    if (i < expr.length() && expr.charAt(i) == '/') {
                        i++;  // 跳过 '/'
                        numBuilder.setLength(0);  // 清空数字构建器
                        while (i < expr.length() && isDigit(expr.charAt(i))) {
                            numBuilder.append(expr.charAt(i++));
                        }
                        denominator = new BigInteger(numBuilder.toString());
                    }

                    // 将解析出的数字和分数压栈
                    numerators.push(numerator);
                    denominators.push(denominator);

                } else if (c == '(') {
                    // 处理左括号,直接压栈
                    operators.push(c);
                    i++;

                } else if (c == ')') {
                    // 处理右括号,进行运算直到遇到左括号
                    while (operators.peek() != '(') {
                        performOperation(numerators, denominators, operators);
                    }
                    operators.pop();  // 弹出 '('
                    i++;

                } else if (isOperator(c)) {
                    // 处理运算符
                    while (!operators.isEmpty() && precedence(c) <= precedence(operators.peek())) {
                        performOperation(numerators, denominators, operators);
                    }
                    operators.push(c);
                    i++;
                }
            }

            // 执行所有剩余的运算
            while (!operators.isEmpty()) {
                performOperation(numerators, denominators, operators);
            }

            // 最终的结果是栈顶的分子和分母
            BigInteger finalNumerator = numerators.pop();
            BigInteger finalDenominator = denominators.pop();

            // 简化分数
            BigInteger gcd = finalNumerator.gcd(finalDenominator);
            finalNumerator = finalNumerator.divide(gcd);
            finalDenominator = finalDenominator.divide(gcd);

            // 返回最简分数形式
            if (finalDenominator.equals(BigInteger.ONE)) {
                return finalNumerator.toString();  // 整数形式
            } else {
                return finalNumerator + "/" + finalDenominator;
            }

        } catch (Exception e) {
            return "Error";  // 捕获错误
        }
    }

5. 单元测试

5.1 覆盖率

  • 使用idea profile

5.2 十条测试细节

基础运算测试:  
@Test
void testBasicOperations() {
    assertEquals("3", Cal.evaluateExpression("1 + 2"));
    assertEquals("1/2", Cal.evaluateExpression("3/4 - 1/4"));
    assertEquals("3/4", Cal.evaluateExpression("1/2 * 3/2"));
    assertEquals("2", Cal.evaluateExpression("4/3 / 2/3"));
}
说明:测试基本的加减乘除运算,确保程序能够正确处理这些操作。  
分数约简测试:  
@Test
void testSimplification() {
    assertEquals("1/2", Cal.evaluateExpression("2/4"));
    assertEquals("3", Cal.evaluateExpression("6/2"));
    assertEquals("5/7", Cal.evaluateExpression("10/14"));
}
说明:测试分数的约简功能,确保程序能够正确简化分数。  
错误处理测试:  
@Test
void testErrorCases() {
    assertEquals("Error", Cal.evaluateExpression("(1 + 2"));
    assertEquals("Error", Cal.evaluateExpression("1 + 2)"));
    assertEquals("Error", Cal.evaluateExpression("1 + a"));
}
说明:测试错误处理,确保程序能够正确识别并处理无效的表达式。  
嵌套运算测试:  
@Test
void testNestedOperations() {
    assertEquals("9/16", Cal.evaluateExpression("(1/2 + (3/4 - 1/8)) * 1/2"));
    assertEquals("1", Cal.evaluateExpression("(1)"));
}
说明:测试嵌套运算,确保程序能够正确处理带有括号的复杂表达式。  
大数运算测试:  
@Test
void testLargeNumbers() {
    assertEquals("1000000000000", Cal.evaluateExpression("1000000000 * 1000"));
    assertEquals("1", Cal.evaluateExpression("1000000000 / 1000000000"));
}
说明:测试大数运算,确保程序能够正确处理大数的计算。  
零除测试:  
@Test
void testDivisionByZero() {
    assertEquals("Error", Cal.evaluateExpression("1 / 0"));
}
说明:测试除以零的情况,确保程序能够正确处理并返回错误。  
负数运算测试:  
@Test
void testNegativeNumbers() {
    assertEquals("-1", Cal.evaluateExpression("1 - 2"));
    assertEquals("-1/2", Cal.evaluateExpression("1/2 - 1"));
}
说明:测试负数运算,确保程序能够正确处理负数的计算。  
多运算符测试  
@Test
void testMultipleOperators() {
    assertEquals("0", Cal.evaluateExpression("1 + 2 - 3"));
    assertEquals("1", Cal.evaluateExpression("1 * 2 / 2"));
}
说明:测试多个运算符的情况,确保程序能够正确处理多个运算符的表达式。  
空格处理测试:  
@Test
void testWhitespaceHandling() {
    assertEquals("3", Cal.evaluateExpression(" 1 + 2 "));
    assertEquals("1/2", Cal.evaluateExpression(" 3 / 4 - 1 / 4 "));
}
说明:测试表达式中的空格处理,确保程序能够正确忽略空格。  
复杂表达式测试:  
@Test
void testComplexExpression() {
    assertEquals("7/8", Cal.evaluateExpression("1/2 + 1/4 + 1/8"));
    assertEquals("1/6", Cal.evaluateExpression("1/2 * 1/3"));
}
说明:测试复杂表达式,确保程序能够正确处理复杂的分数运算。

6. 最终效果

要求 1 2 5 7 8 9展示

java -jar E:\idea-code\cal\cal-1.0-SNAPSHOT.jar -n 10000 -r 100 -e Exercises.txt -a Answers.txt

要求3 4在代码有体现

  // 确保除法结果为真分数
  if (operator.equals("/")) {
      int denominator = random.nextInt(rangeLimit - 1) + 1; // 避免除以零
      expression.append("/").append(denominator);
  } else {
      // 添加运算符和操作数到表达式
      expression.append(operator).append(operand);
  }

7 反思总结

可惜写的太晚了。。。最后没有找到队友

一个人独立完成项目工作量还是比较大,并且缺少团队的反馈

如果有队友,可能可以更好的平衡任务

不过这次让我了解了java的性能分析和测试工具(上次用的Python),并且深入了解了逆波兰表达式和一些异常处理等,收获满满

posted @ 2025-03-21 22:20  Tsukilc  阅读(24)  评论(0)    收藏  举报