自动生成小学四则运算题目

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.1Personal 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后稍微修改一下
  • 因为数据结构定义比较混乱,可拓展性有待考量。
原文地址:https://www.cnblogs.com/yuyifeng/p/12578129.html