这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479 |
这个作业的目标 | 实现四则运算题目生成、答案生成、判对错的需求,接触合作开发项目的流程 |
github地址:https://github.com/xingchen-boot/FourOpsQuiz
项目成员:陈周裕3123004784;林昭南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;}}/*** 转换为字符串表示*/@Overridepublic 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 -jar math-calculator.jar -n 10 -r 10 |
生成10道题目,数值范围1-10 | 通过 |
2 | java -jar math-calculator.jar -n 100 -r 100 |
生成100道题目,数值范围1-100 | 通过 |
3 | java -jar math-calculator.jar -n 0 -r 10 |
错误提示:题目数量必须在1到10000之间 | 通过 |
4 | java -jar math-calculator.jar -n 10001 -r 10 |
错误提示:题目数量必须在1到10000之间 | 通过 |
5 | java -jar math-calculator.jar -n 10 -r 0 |
错误提示:范围参数必须大于等于1 | 通过 |
6 | java -jar math-calculator.jar -e Exercises.txt -a Answers.txt |
正确评分并生成Grade.txt | 通过 |
7 | 生成包含负数结果的表达式 | 该表达式被跳过,不会出现在结果中 | 通过 |
8 | 生成重复表达式 | 重复表达式被过滤,不会出现在结果中 | 通过 |
9 | 生成包含分数的表达式 | 正确处理分数运算 | 通过 |
10 | 生成包含多层括号的表达式 | 正确处理括号优先级 | 通过 |
2. 正确性保证
(1) 单元测试:对核心类和方法进行了全面的单元测试,确保每个组件功能正确。
(2) 边界测试:测试了各种边界情况,如零值、负数、最大数量限制等。
(3) 手动验证:随机抽取生成的题目和答案进行手动计算验证。
(4) 代码审查:团队成员交叉审查代码,确保逻辑正确。
(5) 异常处理:全面的异常处理机制,确保程序在各种情况下都能稳定运行。
六、 项目小结
1. 结对开发感受
通过这次结对项目,我们深刻体会到了团队协作的重要性。结对编程不仅提高了代码质量,还加快了问题解决的速度。在遇到困难时,两个人可以从不同角度思考问题,往往能更快找到解决方案。同时,代码审查过程也帮助我们发现了许多自己可能忽略的问题,提高了代码的健壮性。
2. 经验教训
(1) 时间管理:实际开发时间比预估长了约11.7%,主要是在核心功能实现和测试阶段花费了更多时间。下次项目中需要更准确地评估任务复杂度。
(2) 需求理解:在开发初期,我们对某些需求的理解不够深入,导致后期需要调整。应该在开始编码前确保完全理解所有需求。
(3) 代码复用:在开发过程中,我们发现有些功能可以进一步抽象和复用,减少了重复代码。良好的代码组织和模块化设计非常重要。
(4) 测试驱动开发:采用测试驱动开发可以更早地发现问题,减少后期调试的时间。
3. 后续改进方向
(1) 添加更多题型支持,如填空题、选择题等
(2) 实现更智能的题目难度分级系统
(3) 增加用户账号管理和成绩统计功能
(4) 优化图形界面,提供更好的用户体验
(5) 添加数据持久化,支持题目和成绩的存储与查询
总的来说,这次结对项目是一次非常有价值的经验,不仅提高了我们的编程能力,还培养了团队协作精神。我们相信,这些经验将对未来的学习和工作产生积极影响。