结对项目

| 这个作业属于哪个课程 | 软工1班 |

| 这个作业要求在哪里| 作业三 |

| 这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |

| 项目成员 | 李秉泉312004147|王瑞3123004155 |

github仓库连接:我的仓库

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

一、接口设计和实现过程

类结构

  • Main : 主程序入口
  • AnswerCalculator : 答案生成模块
  • ArithmeticGenerator : 题目生成模块
  • Expression.java : 表达式模块,包含数字表达式和运算符表达式及其父类
  • AnswerGrader : 判定题目和答案的对错,并统计结果
  • FileHandler : 文件读写模块
  • CommandLineParser : 命令行参数处理

二、效能分析



性能提升:使用有限重试+安全降级机制,防止代码死循环,减少递归生成无效子树次数

三、核心代码

//递归生成四则运算表达式树
    private static Expression generateExpression(int opsLeft, int num) {
        //保证题目至少有一个运算符
        if (opsLeft == 0 || random.nextDouble() < 0.3 - 0.1 * opsLeft) {
            return new NumberExpression(generateNumber(num));
        } else {
            // 新增:最大重试次数
            final int MAX_RETRIES = 15;
            int retryCount = 0;

            char op = "+-×÷".charAt(random.nextInt(4));
            int remainingOps = opsLeft - 1;

            // 动态分配剩余配额给左右子树
            int leftOps = random.nextInt(remainingOps + 1);
            int rightOps = remainingOps - leftOps;

            Expression right;
            Expression left;
            Fraction rightVal, leftVal;

            do { // 外层循环控制整个表达式生成
                // 生成右子树(可能包含内层循环)
                right = generateExpression(rightOps, num);
                rightVal = right.calculate();

                // 内层重试机制
                boolean needRetry;
                do {
                    needRetry = false;
                    left = generateExpression(leftOps, num);
                    leftVal = left.calculate();

                    // 条件1:减法校验
                    if (op == '-' && leftVal.subtract(rightVal).isNegative()) {
                        needRetry = true;
                    }

                    // 条件2:除法校验
                    if (op == '÷') {
                        // 除法右子树非零校验
                        while (isZero(rightVal)) {
                            if (++retryCount > MAX_RETRIES) break;
                            right = generateExpression(rightOps, num);
                            rightVal = right.calculate();
                        }

                        // 真分数校验
                        if (!isProperFraction(leftVal.divide(rightVal))) {
                            needRetry = true;
                        }
                    }

                    // 全局结果非负校验
                    if (!needRetry) {
                        Fraction result = switch (op) {
                            case '+' -> leftVal.add(rightVal);
                            case '-' -> leftVal.subtract(rightVal);
                            case '×' -> leftVal.multiply(rightVal);
                            case '÷' -> leftVal.divide(rightVal);
                            default -> throw new IllegalArgumentException("Invalid operator");
                        };
                        if (result.isNegative()) {
                            needRetry = true;
                        }
                    }

                    // 重试控制
                    if (needRetry && ++retryCount > MAX_RETRIES) {
                        // 降级策略:生成最简单的合法表达式
                        return new OperatorExpression('+',
                                new NumberExpression(generateNumber(num)),
                                new NumberExpression(generateNumber(num))
                        );
                    }

                } while (needRetry);

                break; // 退出外层循环
            } while (true);

            return new OperatorExpression(op, left, right);
        }
    }
//将分数转换为最简形式。
    private void normalize() {

        //处理分母为负数的情况。
        if (denominator < 0) { numerator *= -1; denominator *= -1; }
        int gcd = gcd(Math.abs(numerator), denominator);
        numerator /= gcd; denominator /= gcd;

        //将假分数转换为带分数(如 7/4 → `1'3/4
        if (numerator >= denominator) {
            whole += numerator / denominator; numerator %= denominator;
        }
    }
    //计算两个整数的最大公约数,用于分数约分
    private int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
private void advance() {
            // 跳过空格
            if (pos >= input.length()) {
                currentToken = new Token(TokenType.EOF, "");
                return;
            }

            char ch = input.charAt(pos++);
            while (Character.isWhitespace(ch)){
                if (pos >= input.length()) {
                    currentToken = new Token(TokenType.EOF, "");
                    return;
                }
                ch = input.charAt(pos++);
            }

            switch (ch) {
                case '+': currentToken = new Token(TokenType.PLUS, "+"); break;
                case '-': currentToken = new Token(TokenType.MINUS, "-"); break;
                case '×': currentToken = new Token(TokenType.MULTIPLY, "×"); break;
                case '÷': currentToken = new Token(TokenType.DIVIDE, "÷"); break;
                case '(': currentToken = new Token(TokenType.LPAREN, "("); break;
                case ')': currentToken = new Token(TokenType.RPAREN, ")"); break;
                default:
                    if (Character.isDigit(ch) || ch == '\'' || ch == '/') {
                        StringBuilder sb = new StringBuilder();
                        // 提取完整数字/分数
                        sb.append(ch);
                        while (pos < input.length()) {
                            ch = input.charAt(pos);
                            if (Character.isDigit(ch) || ch == '\'' || ch == '/') {
                                sb.append(ch);
                                pos++;
                            } else {
                                break;
                            }
                        }
                        currentToken = new Token(TokenType.NUMBER, sb.toString());
                    } else {
                        throw new IllegalArgumentException("非法字符: " + ch);
                    }
            }
        }

四、项目小结

明确分工与责任意识

项目初期,我们通过讨论明确了彼此的强项:我负责表达式和答案生成以及性能优化,搭档专注表达式答案判定和其余琐碎项。分工后定期同步进度,确保任务不重叠、不遗漏。

沟通机制与冲突解决

使用Git版本控制工具管理代码,避免协作冲突。

建立api文档统一接口要求,协助开发。

互补学习与效率提升

搭档在合作开发的经验弥补了我一张白纸的缺陷,而我在性能优化方面的实践也帮助团队提升了整体效率。

本次合作让我认识到,团队协作不仅是任务的叠加,更是资源整合、沟通协商和信任构建的过程。这些经验将为我未来的团队工作奠定坚实基础。

posted @ 2025-03-22 17:54  linidbas  阅读(20)  评论(0)    收藏  举报