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


    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后稍微修改一下
    • 因为数据结构定义比较混乱,可拓展性有待考量。
  • 相关阅读:
    教你当主管:如何降低你的员工流失率?
    你问我这算什么
    推荐:职场提升的10条捷径
    压力从何而来呢?千万不要2008年结婚
    怎样“管理”你的上司?
    HTTP.sys
    IIS书籍
    IIS目录
    HTTP服务
    在 IIS 7 中使用配置文件
  • 原文地址:https://www.cnblogs.com/yuyifeng/p/12578129.html
Copyright © 2020-2023  润新知