结对项目--小学四则运算题目生成器
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479 |
这个作业的目标 | 实现四则运算题目生成、答案生成、判对错的需求,接触合作开发项目的流程 |
github地址 | https://github.com/xingchen-boot/FourOpsQuiz |
成员1:陈周裕 | 3123004784 |
成员2:林昭南 | 3123004795 |
一、 PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 25 | 25 |
· Estimate | · 估计任务时间 | 25 | 30 |
Development | 开发 | 480 | 500 |
· Analysis | · 需求分析 | 60 | 50 |
· Design Spec | · 生成设计文档 | 45 | 40 |
· Design Review | · 设计复审 | 30 | 25 |
· Coding Standard | · 代码规范制定 | 25 | 20 |
· Design | · 具体设计 | 60 | 70 |
· Coding | · 具体编码 | 250 | 300 |
· Code Review | · 代码复审 | 40 | 50 |
· Test | · 测试 | 29 | 30 |
Reporting | 报告 | 90 | 100 |
· Test Report | · 测试报告 | 25 | 35 |
· Size Measurement | · 计算工作量 | 25 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结 | 40 | 50 |
合计 | 650 | 680 |
二、 效能分析
1. 性能优化时间
在性能优化上,我们花费了约120分钟,比预估的90分钟稍长。主要优化集中在表达式生成和计算部分。
2. 优化思路
(1) 表达式去重优化:使用HashSet
存储已生成的表达式,快速检测重复,避免生成重复题目。
(2) 表达式计算优化:优化了中缀表达式的计算算法,减少了栈操作的次数。
(3) 分数运算优化:实现了高效的分数约分算法,使用最大公约数(GCD)算法简化分数计算。
(4) 文件IO优化:使用缓冲流(BufferedReader/BufferedWriter)代替普通的文件读写,提高了文件操作效率。
3. 性能分析图
性能分析结果(生成1000道题目):
- 表达式生成: 58% (主要消耗)
- 表达式计算: 32%
- 文件IO: 7%
- 其他操作: 3%
4. 消耗最大的函数
根据性能分析,消耗最大的函数是ExpressionGenerator
类中的generateExpressions
方法,该方法负责生成指定数量的唯一四则运算表达式。该函数消耗大的原因是需要频繁生成随机表达式并检查重复,同时还要确保表达式符合所有约束条件(无负数、除法为真分数等)。
三、 设计实现过程
1. 代码组织结构
com.calculator/
├── CalculatorApp.java # 主应用程序类
├── ExpressionGenerator.java # 表达式生成器类
├── ExpressionEvaluator.java # 表达式计算类
├── Fraction.java # 分数类
├── FileHandler.java # 文件处理类
└── util/ # 工具类
└── MathUtils.java # 数学工具类
2. 类关系图
CalculatorApp
↑
├── ExpressionGenerator ←→ Fraction
├── ExpressionEvaluator ←→ Fraction
└── FileHandler
3. 主要类和函数说明
(1) CalculatorApp类
- 主要功能:处理命令行参数,调用其他类完成题目生成和评分功能
- 关键函数:
main()
、generateExercises()
、gradeExercises()
(2) ExpressionGenerator类
- 主要功能:生成四则运算表达式
- 关键函数:
generateExpressions()
、generateExpression()
、generateRandomNumber()
(3) ExpressionEvaluator类
- 主要功能:计算表达式结果
- 关键函数:
evaluate()
、evaluateExpression()
、applyOperator()
(4) Fraction类
- 主要功能:表示分数并提供分数运算
- 关键函数:
add()
、subtract()
、multiply()
、divide()
、reduce()
(5) FileHandler类
- 主要功能:处理文件读写操作
- 关键函数:
writeExpressionsToFile()
、readExpressionsFromFile()
、writeGradeToFile()
4. 关键函数流程图
(1) 表达式生成流程 (ExpressionGenerator.generateExpressions())
(2) 表达式计算流程 (ExpressionEvaluator.evaluateExpression())
(3) 主程序流程 (CalculatorApp.main())
(4) 分数约分流程 (Fraction.reduce())
四、 代码说明
1. 表达式生成关键代码
/**
* 生成指定数量的四则运算表达式
* @param count 表达式数量
* @return 表达式列表
*/
public List<String> generateExpressions(int count) {
List<String> expressions = new ArrayList<>();
int generatedCount = 0;
// 循环生成表达式直到达到指定数量
while (generatedCount < count) {
// 随机决定表达式的复杂度(运算符数量)
int operatorCount = random.nextInt(3) + 1; // 1-3个运算符
// 生成表达式
String expression = generateExpression(operatorCount);
// 检查是否重复
if (generatedExpressions.contains(expression)) {
continue;
}
try {
// 计算表达式结果以验证是否符合要求
Fraction result = ExpressionEvaluator.evaluate(expression);
// 确保结果非负
if (result.isNegative()) {
continue;
}
// 添加到结果列表
expressions.add(expression);
generatedExpressions.add(expression);
generatedCount++;
} catch (Exception e) {
// 忽略无效表达式
continue;
}
}
return expressions;
}
2. 表达式计算关键代码
/**
* 计算表达式的结果
* @param expression 表达式字符串
* @return 计算结果
*/
public static Fraction evaluate(String expression) {
// 移除等号
expression = expression.replace("=", "").trim();
// 处理表达式计算
return evaluateExpression(expression);
}
/**
* 使用两个栈计算表达式
*/
private static Fraction evaluateExpression(String expression) {
// 使用两个栈:一个存储数字,一个存储运算符
Stack<Fraction> numbers = new Stack<>();
Stack<Character> operators = new Stack<>();
int i = 0;
while (i < expression.length()) {
char c = expression.charAt(i);
// 跳过空格
if (c == ' ') {
i++;
continue;
}
// 处理括号
if (c == '(') {
operators.push(c);
i++;
} else if (c == ')') {
// 计算括号内的表达式
while (!operators.isEmpty() && operators.peek() != '(') {
numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
}
operators.pop(); // 弹出左括号
i++;
}
// 处理运算符
else if (isOperator(c)) {
// 处理优先级
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(c)) {
numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
}
operators.push(c);
i++;
}
// 处理数字(整数或分数)
else {
// 解析数字
StringBuilder numBuilder = new StringBuilder();
while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '/' || expression.charAt(i) == '\'')) {
numBuilder.append(expression.charAt(i));
i++;
}
String numStr = numBuilder.toString();
numbers.push(Fraction.parseFraction(numStr));
}
}
// 处理剩余的运算符
while (!operators.isEmpty()) {
numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
}
// 返回最终结果
return numbers.pop();
}
3. 分数运算关键代码
/**
* 分数类,用于表示分数并提供分数运算
*/
public class Fraction {
private long numerator; // 分子
private long denominator; // 分母
// 构造函数
public Fraction(long numerator, long denominator) {
if (denominator == 0) {
throw new IllegalArgumentException("分母不能为0");
}
// 确保分母为正数
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
this.numerator = numerator;
this.denominator = denominator;
// 约分
reduce();
}
/**
* 加法运算
*/
public Fraction add(Fraction other) {
long newNumerator = this.numerator * other.denominator + other.numerator * this.denominator;
long newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator);
}
/**
* 减法运算
*/
public Fraction subtract(Fraction other) {
long newNumerator = this.numerator * other.denominator - other.numerator * this.denominator;
long newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator);
}
/**
* 乘法运算
*/
public Fraction multiply(Fraction other) {
long newNumerator = this.numerator * other.numerator;
long newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator);
}
/**
* 除法运算
*/
public Fraction divide(Fraction other) {
if (other.numerator == 0) {
throw new ArithmeticException("除数不能为0");
}
long newNumerator = this.numerator * other.denominator;
long newDenominator = this.denominator * other.numerator;
return new Fraction(newNumerator, newDenominator);
}
/**
* 约分
*/
private void reduce() {
long gcd = MathUtils.gcd(Math.abs(numerator), denominator);
if (gcd > 0) {
numerator /= gcd;
denominator /= gcd;
}
}
/**
* 转换为字符串表示
*/
@Override
public String toString() {
// 处理整数情况
if (denominator == 1) {
return String.valueOf(numerator);
}
// 处理带分数情况
if (Math.abs(numerator) > denominator) {
long integerPart = numerator / denominator;
long fractionalNumerator = Math.abs(numerator % denominator);
return integerPart + "'" + fractionalNumerator + "/" + denominator;
}
// 普通分数情况
return numerator + "/" + denominator;
}
}
五、 测试运行
1. 命令行测试
(1)题目生成
指令:java com.calculator.CalculatorApp -n 10 -r 10
运行结果:
(2)题目评分
指令:java com.calculator.calculatorApp -e Exercises.txt -a Answers.txt
运行结果:
2. 图形化界面测试
(1)题目生成:
txt文件如下:
(2)题目评分:
txt文件如下:
六、 项目小结
1. 结对开发感受
通过这次结对项目,我们深刻体会到了团队协作的重要性。结对编程不仅提高了代码质量,还加快了问题解决的速度。在遇到困难时,两个人可以从不同角度思考问题,往往能更快找到解决方案。同时,代码审查过程也帮助我们发现了许多自己可能忽略的问题,提高了代码的健壮性。
2. 经验教训
(1) 时间管理:实际开发时间比预估长了约11.7%,主要是在核心功能实现和测试阶段花费了更多时间。下次项目中需要更准确地评估任务复杂度。
(2) 需求理解:在开发初期,我们对某些需求的理解不够深入,导致后期需要调整。应该在开始编码前确保完全理解所有需求。
(3) 代码复用:在开发过程中,我们发现有些功能可以进一步抽象和复用,减少了重复代码。良好的代码组织和模块化设计非常重要。
(4) 测试驱动开发:采用测试驱动开发可以更早地发现问题,减少后期调试的时间。
3. 后续改进方向
(1) 添加更多题型支持,如填空题、选择题等
(2) 实现更智能的题目难度分级系统
(3) 增加用户账号管理和成绩统计功能
(4) 优化图形界面,提供更好的用户体验
(5) 添加数据持久化,支持题目和成绩的存储与查询
总的来说,这次结对项目是一次非常有价值的经验,不仅提高了我们的编程能力,还培养了团队协作精神。我们相信,这些经验将对未来的学习和工作产生积极影响。