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后稍微修改一下
- 因为数据结构定义比较混乱,可拓展性有待考量。