Github项目地址:https://github.com/yyffish/ArithmeticGenerators
结对项目组成员:18软4庾艺锋(3118005117)、18软4王泽鑫(3118005107)
项目相关要求
-
- 使用 -n 参数控制生成题目的个数
- 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
- 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2
- 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数
- 每道题目中出现的运算符个数不超过3个
- 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目
- 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件
- 程序应能支持一万道题目的生成
- 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
困难及解决方法
使用 -n 参数控制生成题目的个数,使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
-
- 用户在前端输入题目数量和数值范围
- 提交表单到servlet进行处理
- 返回生成的算式到前端
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2
-
- 在进行减法运算时,若e1<e2,返回-1
- 在添加到List前进行判断,若算式结果为负数,则不添加到List中
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数
-
- 在进行除法运算时,若e1>e2,返回-1
- 在添加到List前进行判断,若算式结果为负数,则不添加到List中
每道题目中出现的运算符个数不超过3个
-
- 由此可以得知运算符数量为1~3个
- 参数为2~4个
- 用随机数判断运算符数量和参数数量
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目
-
- 用判重函数判断表达式列表中是否有重复项
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件
-
- 将含有括号的表达式转为逆波兰表达式
- 将逆波兰表达式入栈计算结果
- 在添加到List前判重和判负
- 将答案List通过IO流写入到txt文件中
程序应能支持一万道题目的生成
-
- 生成1w到题目测试没问题
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
-
- 用户可以点击检查答案
- 默认检查桌面的Answers.txt和Exercises.txt
- 对比答案并生成饼状图
项目结构
关键代码or设计说明
通过IO流写入到txt文件中
1 package com.AG.dao; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.File; 6 import java.io.FileReader; 7 import java.io.FileWriter; 8 import java.io.IOException; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class FileUtils { 13 14 /** 15 * 创建一个txt文件 16 * 17 * @param filePath 文件路径 18 * @return 创建成功返回true,已存在返回false 19 */ 20 public boolean creatFile(String filePath) { 21 boolean flag = false; 22 File file = new File(filePath); 23 if (file.exists()) { 24 try { 25 file.createNewFile(); 26 } catch (IOException e) { 27 // TODO: handle exception 28 e.printStackTrace(); 29 } 30 flag = true; 31 } 32 return flag; 33 } 34 35 /** 36 * 读取题目文件或答案文件 都是一行一行读取 37 * 38 * @param filePath 文件路径 39 * @return 返回题目或答案List 40 */ 41 public List<String> readTxtFile(String filePath) { 42 List<String> list = new ArrayList<String>(); 43 String thisLine = null; 44 File file = new File(filePath); 45 if (file.exists() && file.isFile()) { 46 try { 47 BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); 48 while ((thisLine = bufferedReader.readLine()) != null) { 49 list.add(thisLine); 50 } 51 bufferedReader.close(); 52 } catch (Exception e) { 53 // TODO: handle exception 54 e.printStackTrace(); 55 } 56 } 57 return list; 58 } 59 60 /** 61 * 读txt文件 62 * 63 * @param list 64 * @param filePath 65 * @return 66 */ 67 public boolean writeTxtFile(List<String> list, String filePath) { 68 boolean flag = false; 69 File file = new File(filePath); 70 try { 71 if (!file.exists()) { 72 file.createNewFile(); 73 } 74 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath)); 75 for (String string : list) { 76 bufferedWriter.write(string); 77 bufferedWriter.newLine(); 78 } 79 bufferedWriter.close(); 80 } catch (IOException e) { 81 // TODO: handle exception 82 e.printStackTrace(); 83 } 84 flag = true; 85 return flag; 86 } 87 88 }
通过逆波兰表达式计算结果
- 如有表达式 1 2 3 + +
- 1 2 3 依次入栈
- 遇到 + 号,2 3 出栈 进行加法运算 结果为 5
- 5入栈
- 遇到 + 号, 5 1出栈 再进行加法运算 结果为 6
- 栈空 结果为6
1 /** 2 * 通过逆波兰表达式计算结果 3 * 4 * @param 表达式 5 * @return Stringl类型的计算结果 6 */ 7 private String calcRevPolishNotation(String express) { 8 Stack<String> stack = new Stack<>(); 9 String[] expressArr = express.split(" "); 10 for (int i = 0; i < expressArr.length; i++) { 11 if (expressArr[i].matches("[0-9]+/[0-9]+")) { 12 stack.push(expressArr[i]); 13 // + - * / 运算符的处理 14 } else if (expressArr[i].matches("[\+\-\*\÷]")) { 15 String k1 = stack.pop(); 16 String k2 = stack.pop(); 17 // 计算结果 18 String res = calcValue(k1, k2, expressArr[i]); 19 stack.push(res); 20 } 21 22 } 23 return stack.pop(); 24 }
将中缀表达式转换为后缀表达式(逆波兰表达式)
- 就是通过栈来讲公式转为逆波兰表达式 如 1 + ( 2 + 3 ) => 1 2 3 + +
- 数字直接入栈
- 如果是运算符 则将栈中的两个数字出栈进行运算
- 若是括号 我还没理解
1 /** 2 * 将中缀表达式转换为后缀表达式(逆波兰表达式) 3 * 4 * @param express 5 * @return 6 */ 7 private String transfer(String express) { 8 Stack<String> stack = new Stack<>(); 9 List<String> list = new ArrayList<>(); 10 String[] expressArr = express.split(" "); 11 for (int i = 0; i < expressArr.length; i++) { 12 if (expressArr[i].matches("[0-9]+/[0-9]+")) { 13 list.add(expressArr[i]); 14 } else if (expressArr[i].matches("[\+\-\*\÷]")) { 15 // 如果stack为空 16 if (stack.isEmpty()) { 17 stack.push(expressArr[i]); 18 continue; 19 } 20 // 不为空 21 22 // 上一个元素不为(,且当前运算符优先级小于上一个元素则,将比这个运算符优先级大的元素全部加入到队列中 23 while (!stack.isEmpty() && !stack.lastElement().equals("(") 24 && !comparePriority(expressArr[i], stack.lastElement())) { 25 list.add(stack.pop()); 26 } 27 stack.push(expressArr[i]); 28 } else if ("(".equals(expressArr[i])) { 29 // 遇到左小括号无条件加入 30 stack.push(expressArr[i]); 31 } else if (")".equals(expressArr[i])) { 32 // 遇到右小括号,则寻找上一堆小括号,然后把中间的值全部放入队列中 33 while (!("(").equals(stack.lastElement())) { 34 list.add(stack.pop()); 35 } 36 // 上述循环停止,这栈顶元素必为"(" 37 stack.pop(); 38 } 39 } 40 // 将栈中剩余元素加入到队列中 41 while (!stack.isEmpty()) { 42 list.add(stack.pop()); 43 } 44 StringBuffer stringBuffer = new StringBuffer(); 45 // 变成字符串 46 for (String s : list) { 47 stringBuffer.append(s + " "); 48 } 49 return stringBuffer.toString(); 50 }
辗转相除法求公约数
1 /** 2 * 辗转相除法求公约数 3 * 4 * @param numerator 分子 5 * @param denominator 分母 6 * @return 最大公约数 7 */ 8 public Integer gcd(Integer numerator, Integer denominator) { 9 int temp; 10 while (denominator > 0) { 11 temp = numerator % denominator; 12 numerator = denominator; 13 denominator = temp; 14 } 15 return numerator; 16 }
比较两条表示式是否相同
- 通过Set的不可重复来校验参数列表ParameterList和运算符列表OperatorList是否重复
- 因为想不出可以区别两个表达式的算法,我们就折衷了一下,只要判断两个表达式的操作数还有运算符完全相同,则判定两个表达式相同。
- 这种判断方式是有问题的,会把一些不是相同的表达式误判为相同,黔驴技穷之下,顾不了这么多了,能满足需求就行了,以后有新的想法再进行优化。
1 /** 2 * 比较express表达式是否存在于examList表达式列表中 3 * 4 * @param examList 表达式列表 5 * @param express 表达式 6 * @return 结果 存在返回true 不存在返回false 7 */ 8 public Boolean isExistList(List<Expression> examList, Expression express) { 9 int len = examList.size(); 10 for (int i = 0; i < len; i++) { 11 if (isSame(express, examList.get(i))) 12 return true; 13 } 14 return false; 15 } 16 17 /** 18 * 比较两条表示式是否相同 19 * 20 * @param express1 表达式1 21 * @param express2 表达式2 22 * @return 相同返回true,否则返回false 23 */ 24 private Boolean isSame(Expression express1, Expression express2) { 25 // 运算符数目不相等,直接返回 26 if (express1.getOperatorList().size() != express2.getOperatorList().size()) 27 return false; 28 Set<Map<Integer, Integer>> parameterSet = new HashSet<Map<Integer, Integer>>(); 29 Set<String> operatorSet = new HashSet<String>(); 30 List<Map<Integer, Integer>> parameterList = express1.getParameterList();// 获取表达式1的参数列表 31 List<String> operatorList = express1.getOperatorList();// 获取表达式1的操作符列表 32 int len = operatorList.size(); 33 // 将参数和操作符列表分别加进Set 34 for (int i = 0; i < len; i++) { 35 operatorSet.add(operatorList.get(i)); 36 parameterSet.add(parameterList.get(i)); 37 } 38 parameterSet.add(parameterList.get(len)); 39 parameterList = express2.getParameterList();// 获取表达式2的参数列表 40 operatorList = express2.getOperatorList();// 获取表达式2的操作符列表 41 for (int i = 0; i < len; i++) { 42 if (!operatorSet.contains(operatorList.get(i)) || !parameterSet.contains(parameterList.get(i))) { 43 return false; 44 } 45 } 46 if (!parameterSet.contains(parameterList.get(len))) 47 return false; 48 return true; 49 }
运行测试
向服务器发送请求 http://localhost:8080/ArithmeticGenerators/tableForm.jsp
用户输入题目数量和最大值,提交form表达,发送exam请求
生成从1~10的题目
随意修改几个答案后进行答案校验
PSP
因为项目用时较长,以下时间都是预估的
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 90 | 90 |
· Estimate | · 估计这个任务需要多少时间 | 90 | 90 |
Development | 开发 | 2140 | 2140 |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 30 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 120 | 120 |
· Coding | · 具体编码 | 2000 | 2000 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 50 | 50 |
Reporting | 报告 | 110 | 110 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2540 | 2540 |
项目总结
- 作为结对项目,最重要的是合作。我认为这次不足的就是队友之间的相互配合了。虽然有疫情的原因,不能够面对面交流,经常出现队友不能理解我的思路而不能编写出合适的代码。在这方面还是需要多项有经验的同学请教。
- 数据结构定义比较混乱,应该少使用Map和List,应尽量定义为数组
- 本题的唯一算法就是逆波兰表达式的转换,尽管研究了很久,但还是看不懂,只能copy后稍微修改一下
- 因为数据结构定义比较混乱,可拓展性有待考量。