结对项目

信息类别 具体内容
所属课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/
作业要求 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479
作业目标 通过结对编程实践的完整开发流程和团队协作开发一个自动生成与批改系统
队员一 计科 3 班 丁温婕3223004253
队员二 计科 3 班 吴静欣3223004300

github链接:https://github.com/wjx4300/MathExercise

一、PSP表格

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

二、效能分析

  1. 性能改进记录
    问题分析与定位: 0.5小时
    算法重构与优化: 1小时
    测试验证与调试: 0.5小时
  2. 改进思路
  • Fraction.gcd() - 最大公约数计算
    问题:每次创建分数都要计算GCD
    时间复杂度:O(log min(a,b))
    影响:频繁调用,累积开销大

    • 使用欧几里得算法优化的GCD计算
      实现分数对象池,减少对象创建开销
      添加分数缓存,避免重复计算
  • normalize() - 表达式标准化
    问题:字符串分割、排序、拼接
    时间复杂度:O(n log n)
    影响:去重时频繁调用

    • 优化
      使用StringBuilder替代字符串拼接
      优化正则表达式,使用更高效的匹配
      实现表达式预编译,避免重复解析
  • generateExpr() - 递归表达式生成
    问题:递归调用深度大,每次生成都要重新计算
    时间复杂度:O(3^n) 其中n是运算符数量
    影响:生成1000道题目时可能调用数万次

    • 表达式生成优化
      使用预计算表达式模板,避免重复递归
      实现表达式缓存,避免重复计算相同表达式
      优化运算符分配算法,减少无效生成

三、设计实现过程
核心类关系

mathExercise/
├── mathExercise.java          # 主程序入口
├── Fraction.java              # 分数类(核心计算)
├── Expression.java            # 表达式类
├── ExerciseGenerator.java     # 题目生成器
├── AnswerGrader.java          # 答案批改器

具体设计
3.1 Fraction类 - 分数计算核心

public class Fraction {
    // 核心属性
    private long numerator;     // 分子
    private long denominator; // 分母
    private long integer;      // 整数部分
    
    // 核心方法
    public Fraction add(Fraction f)           // 加法
    public Fraction subtract(Fraction f)     // 减法
    public Fraction multiply(Fraction f)     // 乘法
    public Fraction divide(Fraction f)        // 除法
    public boolean greaterOrEqual(Fraction f) // 比较
    public static Fraction parse(String s)    // 解析
    public String toString()                  // 转换
    private long gcd(long a, long b)          // 最大公约数
}

3.2 Expression类 - 表达式管理

public class Expression {
    private String exprStr;    // 表达式字符串
    private Fraction result;   // 计算结果
    private ExprNode tree;     // 表达式树
    
    // 核心方法
    public static Expression parse(String expr)     // 解析表达式
    public static String normalize(String expr)    // 标准化
    public boolean equals(Object obj)              // 比较
    private ExprNode buildTree(String expr)        // 构建树
}

3.3 ExerciseGenerator类 - 题目生成

public class ExerciseGenerator {
    private Random random;
    private Set<String> uniqueExpressions;
    private Map<String, Expression> expressionCache;
    
    // 核心方法
    public List<Expression> generate(int count, int range)  // 生成题目
    private Expression generateSingle(int ops, int range)   // 生成单个
    private Fraction generateNumber(int range)              // 生成数字
    private String normalize(String expr)                   // 去重标准化
}

3.4 AnswerGrader类 - 答案批改

public class AnswerGrader {
    // 核心方法
    public GradeResult grade(String exerciseFile, String answerFile)  // 批改
    private Fraction parseAndCalculate(String expr)                   // 解析计算
    private List<String> readLines(String file)                       // 读取文件
}

3.5 流程图

  • 题目生成
    image

  • 答案批改
    image

  • 分数计算
    image

四、代码说明

/**
 * 生成题目主函数 - 核心思路:递归生成 + 去重
 * 设计思路:
 * 1. 随机生成运算符数量(1-3个)
 * 2. 递归生成左右子树
 * 3. 验证结果有效性
 * 4. 去重处理
 */
private static void generate(int n, int r) throws IOException {
    Set<String> unique = new HashSet<>();  // 去重集合
    List<Expr> exprs = new ArrayList<>();  // 结果列表

    // 循环生成直到达到指定数量
    while (exprs.size() < n) {
        int ops = random.nextInt(3) + 1; // 1-3个运算符
        Expr expr = generateExpr(ops, r);
        
        // 去重检查:标准化后加入Set
        if (expr != null && unique.add(normalize(expr.exprStr))) {
            exprs.add(expr);
        }
    }

    // 写入文件
    try (BufferedWriter exWriter = new BufferedWriter(new FileWriter(EXERCISES_FILE));
         BufferedWriter anWriter = new BufferedWriter(new FileWriter(ANSWERS_FILE))) {
        for (int i = 0; i < exprs.size(); i++) {
            exWriter.write((i + 1) + ". " + exprs.get(i).exprStr + " =");
            exWriter.newLine();
            anWriter.write((i + 1) + ". " + exprs.get(i).result.toString());
            anWriter.newLine();
        }
    }
}
/**
 * 递归生成表达式 - 核心思路:分治算法
 * 算法复杂度:O(3^n) - 这是性能瓶颈所在
 */
private static Expr generateExpr(int ops, int r) {
    try {
        // 递归终止条件:无运算符,生成单个数字
        if (ops == 0) {
            Fraction f = generateNumber(r);
            return new Expr(f.toString(), f);
        }

        // 分治:将运算符分配给左右子树
        int leftOps = random.nextInt(ops);      // 左子树运算符数
        int rightOps = ops - leftOps - 1;       // 右子树运算符数
        
        // 递归生成左右子树
        Expr left = generateExpr(leftOps, r);
        Expr right = generateExpr(rightOps, r);
        if (left == null || right == null) return null;

        // 随机选择运算符
        char[] opsArr = {'+', '-', '×', '÷'};
        char op = opsArr[random.nextInt(opsArr.length)];

        // 业务规则验证
        // 规则1:减法结果必须非负
        if (op == '-' && !left.result.greaterOrEqual(right.result)) {
            return null;
        }
        
        // 规则2:除法除数非零且结果分母在范围内
        if (op == '÷') {
            try {
                Fraction div = left.result.divide(right.result);
                if (div.denominator > r) return null; // 分母超出范围
            } catch (Exception e) {
                return null;
            }
        }

        // 计算结果
        Fraction res = calculate(left.result, right.result, op);
        if (res == null) return null;

        // 构建表达式字符串(括号处理)
        String leftStr = (leftOps > 0) ? "(" + left.exprStr + ")" : left.exprStr;
        String rightStr = (rightOps > 0) ? "(" + right.exprStr + ")" : right.exprStr;
        String exprStr = leftStr + " " + op + " " + rightStr;

        return new Expr(exprStr, res);
    } catch (Exception e) {
        return null;
    }
}
/**
 * 随机数生成 - 核心思路:多样化数字类型
 * 设计思路:50%概率生成整数,50%概率生成分数
 */
private static Fraction generateNumber(int r) {
    if (random.nextBoolean()) {
        // 生成自然数(1到r-1,避免0过多)
        return new Fraction(random.nextInt(r - 1) + 1);
    } else {
        // 生成分数(真分数或带分数)
        long den = random.nextInt(r - 1) + 1;           // 分母1到r-1
        long num = random.nextInt((int) den);            // 分子0到den-1
        long integer = random.nextBoolean() ? random.nextInt(r - 1) : 0; // 整数部分0到r-2
        return new Fraction(integer * den + num, den);
    }
}

五、测试运行
*练习题
image
*正确答案
image
*我的答案
image
*得分
image

六、项目小结
这次双人合作项目,不仅成功交付了一款功能较完整、质量较可靠的自动生成四则运算题目的系统,更让我们收获了宝贵的协作经验。我们深刻认识到,双人合作的核心不是 “简单分工”,而是 “优势互补、相互成就”—— 通过合理分工提升效率,通过交叉校验保障质量,通过思维碰撞突破瓶颈。更重要的是,这次合作还让我们理解到了团队协作的真谛:优秀的项目从来不是一个人的功劳,而是两个人同心协力、互相支撑的结果。这段经历将成为我们未来学习和工作中的重要财富,激励我们在团队协作中不断成长,创造更多价值。
我的队友吴静欣严谨认真,对负责的部分完成的很棒,细节处理得很仔细完全没有bug,简直太优秀了!
我的队友丁温婕做事细心严谨,测试用例设计全面,逻辑思维清晰,问题解决能力强,与她沟通合作非常流畅。但可以再提升一下项目规划前瞻性,减少后期返工。

posted @ 2025-10-22 00:29  vvvvwj  阅读(12)  评论(0)    收藏  举报