结对项目
| 这个作业属于哪个课程 | 课程链接 |
|---|---|
| 这个作业要求在哪里 | 作业链接 |
| 这个作业的目标 | 体验结对项目,学习如何进行项目合作与分工 |
结对学生信息
| 姓名 | 学号 | GitHub项目地址 |
|---|---|---|
| 罗佳楠 | 3223004600 | https://github.com/changlu812/co-work |
| 刘雨彤 | 3223004599 | https://github.com/LYTfor/MathExerciseGenerator/tree/main/primatrain |
PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 640 | 740 |
| · Estimate | · 估计这个任务需要多少时间 | 640 | 740 |
| Development | 开发 | 520 | 570 |
| · Analysis | · 需求分析 (包括学习新技术) | 120 | 120 |
| · Design Spec | · 生成设计文档 | 50 | 50 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 50 | 50 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
| · Design | · 具体设计 | 100 | 100 |
| · Coding | · 具体编码 | 120 | 170 |
| · Code Review | · 代码复审 | 30 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 30 | 30 |
| Reporting | 报告 | 120 | 170 |
| · Test Report | · 测试报告 | 80 | 100 |
| · Size Measurement | · 计算工作量 | 20 | 40 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
| 合计 | 640 | 740 |
效能分析
性能分析图

本图使用JProfiler进行分析,其中消耗最大的函数是java.util.Scanner - 63.8% (3,800ms),原因是等待用户输入占用了大部分时间;其次是ImprovedExpressionGenerator - 32.4% (1,930ms),这是业务的核心逻辑,是生成表达式和计算的实际耗时。
优化分析
程序的主要问题是被Scanner等待输入严重干扰了,导致数据输入不准确,优化思路是考虑移除Scanner
设计实现过程
数据流设计
用户输入 → 参数解析 → 表达式生成 → 验证计算 → 文件输出
↓
分数运算 → 结果规范化
类关系图
主程序类 → 表达式生成器类 → 分数类
↓
文件操作工具类
主要数据结构
- Fraction: 封装分子、分母、整数部分
- Set
: 存储表达式哈希,避免重复 - List
: 存储题目和答案列表 - Map<String, Integer>: 运算符优先级映射
核心类及关键函数流程图
Fraction 类 - 分数处理核心
类职责:
- 表示和操作分数
- 处理分数的规范化、运算和比较
- 提供分数与字符串的相互转换
normalize() 方法:
![image]()
add() 方法:

ImprovedExpressionGenerator 类 - 表达式生成核心
类职责:
- 生成合法的数学表达式
- 验证表达式的合法性
- 计算表达式结果
- 避免重复表达式
generateExpression() 方法:
![image]()
isExpressionValid() 方法:
calculateComplexExpression() 方法:

MathExerciseGenerator 类 - 主程序控制
类职责:
- 解析命令行参数
- 协调整个程序流程
- 处理文件输入输出
- 提供用户界面
main() 方法:
![image]()
generateExercises() 方法:

gradeExercises() 方法:

项目关键代码与说明
分数规范化算法 - Fraction.normalize()
private void normalize() {
// 处理假分数:将分子超过分母的部分转为整数部分
if (numerator >= denominator) {
whole += numerator / denominator; // 计算整数部分
numerator %= denominator; // 分子取余数
}
// 计算最大公约数进行约分
int gcd = gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
// 处理分子为0的情况,分母设为1统一格式
if (numerator == 0) denominator = 1;
}
设计思路:
- 确保分数始终以最简形式存储
- 自动处理假分数转换
- 统一零的表示格式
递归GCD计算 - Fraction.gcd()
private int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b); // 欧几里得算法
}
设计思路:
- 使用经典的欧几里得算法
- 递归实现,代码简洁高效
- 确保分数约分的正确性
表达式生成主循环 - ImprovedExpressionGenerator.generateExpression()
public String[] generateExpression(int maxAttempts) {
for (int attempt = 0; attempt < maxAttempts; attempt++) {
try {
int operatorCount = random.nextInt(3) + 1; // 随机1-3个运算符
String[] result = generateValidExpression(operatorCount);
// 使用哈希去重,确保题目唯一性
if (result != null && !expressionHashes.contains(result[2])) {
expressionHashes.add(result[2]);
return new String[]{result[0], result[1]}; // 返回[表达式, 答案]
}
} catch (Exception e) {
// 忽略单次生成失败,继续尝试
}
}
return null; // 达到最大尝试次数仍未生成成功
}
设计思路:
- 重试机制:允许生成失败时自动重试
- 去重机制:使用哈希集合避免重复题目
- 异常隔离:单次生成失败不影响整体流程
复杂表达式验证 - ImprovedExpressionGenerator.isExpressionValid()
private boolean isExpressionValid(String expression) {
try {
String tempExpression = expression;
// 递归处理括号表达式:从最内层括号开始计算
while (tempExpression.contains("(")) {
int start = tempExpression.lastIndexOf("("); // 找到最内层左括号
int end = tempExpression.indexOf(")", start); // 匹配右括号
String subExpr = tempExpression.substring(start + 1, end);
// 验证子表达式合法性
if (!isSimpleExpressionValid(subExpr)) {
return false;
}
// 计算子表达式并用结果替换原表达式
Fraction subResult = calculateSimple(subExpr);
tempExpression = tempExpression.substring(0, start) +
subResult.toString() +
tempExpression.substring(end + 1);
}
// 验证无括号的简单表达式
return isSimpleExpressionValid(tempExpression);
} catch (Exception e) {
return false; // 任何异常都视为表达式不合法
}
}
设计思路:
- 递归消括号:从最内层括号开始逐层计算
- 渐进验证:确保每一步运算都合法
- 异常安全:任何计算异常都导致验证失败
题目生成主流程 - MathExerciseGenerator.generateExercises()
private static void generateExercises(int count, int range) {
ImprovedExpressionGenerator generator = new ImprovedExpressionGenerator(range);
List<String> exercises = new ArrayList<>();
List<String> answers = new ArrayList<>();
long startTime = System.currentTimeMillis();
int generated = 0;
// 生成指定数量的题目
for (int i = 0; i < count; i++) {
String[] result = generator.generateExpression(100); // 最多尝试100次
if (result != null) {
// 格式化题目和答案
exercises.add((i+1) + ". " + result[0]); // "1. 1/2 + 1/3 ="
answers.add((i+1) + ". " + result[1]); // "1. 5/6"
generated++;
}
}
long endTime = System.currentTimeMillis();
// 保存到文件
writeToFile("Exercises.txt", exercises);
writeToFile("Answers.txt", answers);
}
设计思路:
- 批量生成:支持生成大量题目
- 进度统计:记录成功生成的数量
- 性能监控:计算总耗时
答案批改核心 - MathExerciseGenerator.gradeExercises()
private static void gradeExercises(String exerciseFile, String answerFile) {
List<String> exercises = readFile(exerciseFile);
List<String> studentAnswers = readFile(answerFile);
List<Integer> correct = new ArrayList<>();
List<Integer> wrong = new ArrayList<>();
for (int i = 0; i < Math.min(exercises.size(), studentAnswers.size()); i++) {
String exercise = parseExercise(exercises.get(i)); // 解析题目
String studentAnswer = parseAnswer(studentAnswers.get(i)); // 解析答案
try {
Fraction correctAnswer = ImprovedExpressionGenerator.calculateExpressionForGrading(exercise);
Fraction studentAnswerFraction = Fraction.parseFraction(studentAnswer);
// 分数比较:使用equals方法(已重写)
if (correctAnswer.equals(studentAnswerFraction)) {
correct.add(i + 1);
} else {
wrong.add(i + 1);
}
} catch (Exception e) {
wrong.add(i + 1); // 计算或解析异常视为错误
}
}
saveGradeResult(correct, wrong); // 保存批改结果
}
设计思路:
- 容错处理:解析异常自动标记为错误
- 精确比较:使用重写的equals方法比较分数
- 结果持久化:保存批改结果到文件
测试运行
生成10道计算题

检查答案

程序支持一次性10000道题目生成


程序可以正确批改答案并指出错误题目


批改结果(grade.txt)

项目小结
本次项目是第一次结对项目,在项目过程中,我们通过合理分工,极大地提高了工作效率,同时我们也发现了一些问题,比如两个人电脑环境配置不同导致相同项目文件在一台机器无法运行的情况,因此得出教训:在正式开始开发前应当两人协调确认好开发环境以免拖累项目进度。
结对感受:人多力量大




浙公网安备 33010602011771号