Java实现简单自动生成小学四则运算题目

项目语言:Java(JDK1.3及以上);

编译运行平台:IntelliJ IDEA;

作者:麦狄龙;马志鹏;

1.GitHub项目网址:

网址:https://github.com/MDL-MZP/FourFundamentalRules

项目版本下载(当前时间最新版:v2.0):https://github.com/MDL-MZP/FourFundamentalRules/tags

2.个人PSP

PSP(严格来说是基于团队的PSP)

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划 10  
· Estimate · 估计这个任务需要多少时间 10  
Development 开发 1015  
· Analysis · 需求分析 (包括学习新技术) 120  
· Design Spec · 生成设计文档 45  
· Design Review · 设计复审 (和同事审核设计文档) 10  
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 120  
· Design · 具体设计 300  
· Coding · 具体编码 300  
· Code Review · 代码复审 120  
· Test · 测试(自我测试,修改代码,提交修改) 60  
Reporting 报告 90  
· Test Report · 测试报告 60  
· Size Measurement · 计算工作量 10  
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20  
合计   1125  

3.题目介绍与解题思路

3.1题目介绍:

  1. 使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。

  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 Myapp.exe -r 10 ,将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。

  3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。

  4. 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数

  5. 每道题目中出现的运算符个数不超过3个。

  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。

  7. 生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:

    四则运算题目1

    四则运算题目2

    ……

    其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

    在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:

    1.答案1

    2.答案2

    特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。

  8. 程序应能支持一万道题目的生成。

  9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:

    Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt

    统计结果输出到文件Grade.txt,格式如下:

    Correct: 5 (1, 3, 5, 7, 9)

    Wrong: 5 (2, 4, 6, 8, 10)

    其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

3.2解题思路:

分解需求

  • 生成计算表达式:

    • 第一步先忽略括号的生成,数字和符号随机生成

    • 第二步再在第一步的基础上随机在数字周围生成括号

    • 删除一些不规范的括号

  • 筛选不符合规则表达式:

    • 一开始想到用树结构将表达式分解,获取符号优先级,那么就判断减号结点的左子树结果减去右子树结果是否为负数;除号结点的右子树是否为0;两条表达式生成两棵树,层次遍历两棵树可判断是否重复

    • 后来决定分级别筛选,判断子表达式是否为负数和是否有除数为0的列为粗略筛选,可简单用递归的思想解决

    • 而判断是否生成重复表达式,根据式子答案是否相同、长度是否相同、运算符优先级别是否相同、层次遍历是否相同作为精细筛选

  • 用户输入答案:考虑到后面有一个选择:让用户决定是否校对答案, 就想着”用控制台输入信息“这个点卡住让程序停在这里,然后趁这段时间直接在生成的Exercise.txt文件中进行答题,控制台输入信息后再读取文件,拿到用户答案。但是失败了,读取Exercise.txt文件没有用户输入的答案。个人猜测是文件能read到的部分应该要write进去才行,而手动输入不行。最后把问题转变到控制台输出表达式,在控制台输入获取用户答案。

  • 答案校对:

    • 生成答案:可以用递归表达式,根据符号优先级一步一步算子表达式,最后得出结果;也可用直接遍历表达式,获取符号优先级,将数字强制放在运算符左右,根据优先级一路进行计算

    • 校对答案,比较正确答案和用户答案两个数组内容,同一下标视为同一道题,便可知道答案正确与否

4.设计实现过程

4.1项目总体布局:

设计一个主类FourFundamental,用来调用其他功能类。

功能类包括Accuracy(输出正确率类)、CorrectAnswer(输出正确答案类)、Screen类(表达式筛选类)、 CreateCorrectExpression(创建正确表达式类)、File IO(文件读写类)、Input(控制台信息输入输出类)

当运行程序时,Team目录下会自动生成练习文件、答案文件、以及成绩文件

4.2具体类与方法说明与其之间的调用

5.关键代码展示与说明

5.1项目入口:

 1 /**
 2  * @date 2020.03.31
 3  * @author   Mazin and Uncle-Drew  🌼
 4  * 主类
 5  */
 6 public class FourFundamentalRules {
 7  8  9     public static void main(String[] args) throws Exception {
10         /**
11          * 提要求:给用户说明怎么使用以及实现最后文件自动删除
12          */
13         File file = new File("");
14         String file_path = file.getAbsolutePath();
15         File f1 = new File(file_path+"\\Exercise.txt");
16         File f2 = new File(file_path+"\\Answer.txt");
17         File f3 = new File(file_path+"\\Grade.txt");
18         FileIO.createOrFlush(f1);
19         FileIO.createOrFlush(f2);
20         FileIO.createOrFlush(f3);
21         //  输入一些基本信息,如要生成的表达式数目num,和表达式的数值范围limits
22         Input.inputBase();
23         //  输出表达式,等待用户作答,num:表达式数目,limits:表达式的数值范围
24         CreateCorrectExpression.outCorrectExpression(Input.num, Input.limits,f1,f2);
25         // 用户答案获取
26         String[] useranswers = Input.usersAnswer();
27         // 如果要输出正确率,输入正确命令,表示输出自己答题情况
28         boolean tag = Input.inputExpand();
29         if (tag) {
30             System.out.println("及时检查自己的作业哦");
31             //  写入文件
32             Accuracy.result(useranswers,f3);
33         }else{
34             System.out.println("感谢您的使用");
35         }
36     }
37 38 }

5.2关键类与方法

  • CreateCorrectExpression:用于生成正确表达式工具类

      1 /**
      2  *
      3  * @author Mazin and Uncle-Drew
      4  *
      5  *  最终输出正确的表达式
      6  *
      7  */
      8 public class CreateCorrectExpression {
      9 
     10     private static String[] questions = new String[Input.num];
     11     private static String[] answers = new String[Input.num];
     12 
     13     public static String[] getQuestions(){
     14         return questions;
     15     }
     16 
     17     public static String[] getAnswers(){
     18         return answers;
     19     }
     20 
     21     /**
     22      * 生成一条表达式
     23      * @param limits  数值范围限制
     24      * @return 一条初始表达式
     25      */
     26     public static String createExpression(int limits){
     27         StringBuffer expression = new StringBuffer();
     28         // 4个运算符随机选择
     29         String character = "+-×÷";
     30         int n;
     31         char c;
     32         int charNum = 0;
     33         Random r = new Random();
     34         // 表达式字符个数charNum 1,2,3随机
     35         charNum = r.nextInt(3) + 1;
     36         for(int countCharNum = 0;countCharNum < charNum; countCharNum++) {
     37             // 生成1个数 , 范围  [0,in)
     38             n = r.nextInt(limits);
     39             //   拿到符号
     40             c = character.charAt(r.nextInt(character.length()));
     41             //   每一趟都是  数字和运算符连接
     42             expression.append(String.valueOf(n) + c);
     43         }
     44         // 表达式出来后末尾是运算符,需要在加一个数字
     45         n = r.nextInt(limits);
     46         expression.append(n);
     47         // 加括号
     48         expression = addBrackets(expression);
     49         return expression.toString();
     50     }
     51 
     52     /**
     53      *  筛选正确算术表达式,写入文件Exercise.txt,并做出答案,写入文件Answer.txt
     54      * @param num    生成的表达式数目
     55      * @param limits  表达式中数值的范围
     56      * @param exercise  练习文件
     57      * @param answer    答案文件
     58      * @throws Exception
     59      */
     60     public static void outCorrectExpression(int num, int limits, File exercise,File answer) throws Exception{
     61         int i = 0;
     62         Screen screen = new Screen();
     63         //粗略+精细筛选
     64         x:
     65         while (i != num){
     66             //生成的未判断表达式
     67             String exps = createExpression(limits);
     68             if (!screen.roughScreen(exps).equals("false")){
     69                 questions[i] = exps + " =";
     70                 i++;//最后i等于num
     71             }else {
     72                 continue x;
     73             }
     74         }
     75         // 正确的表达式 写入练习文件
     76         FileIO.write(exercise,questions);
     77         // 做出答案
     78         for(int j = 0;j < num ;j++){
     79             answers[j] = CorrectAnswer.answer(questions[j]);
     80         }
     81         // 写入答案文件
     82         FileIO.write(answer,answers);
     83     }
     84 
     85     /**
     86      *   对整个表达式,将(1)这种括号将数字包含的情况彻底删除
     87      * @param expression 表达式的StringBuffer类型
     88      */
     89     private static void standard(StringBuffer expression){
     90         //  从1开始,length-2结束,是因为,如果0,length-1是数字,那么,不可能有()包住的情况
     91         for(int i = 1; i < expression.length()-1; i++){
     92             int l = 1,r = 1;
     93             // 数字
     94             boolean tag = expression.charAt(i) >= 48 && expression.charAt(i) <= 57;
     95             if(tag) {
     96                 //  l定位到数字左端
     97                 l = leftMove(expression, i);
     98                 // 数字最左端是开头,变为1
     99                 if(l == 0){
    100                     l = 1;
    101                 }
    102                 //  r定位到数字右端
    103                 r = rightMove(expression, i);
    104                 //  数字左边为“(”  同时右边为“)”
    105                 if (expression.charAt(l - 1) == '(' && expression.charAt(r + 1) == ')') {
    106                     expression.deleteCharAt(l - 1);
    107                     // 已经删除了一个 ( ,原先在i+1处的 ) 现在在 i 处
    108                     expression.deleteCharAt(r);
    109                 }
    110                 // 经过上面的修改或移位,i可以跳过循环的一些过程
    111                 i = r;
    112             }
    113         }
    114     }
    115 
    116     /**
    117      *  判断是否删除掉首尾的括号
    118      * @param expression 表达式的StringBuffer类型
    119      * @return 返回表达式去除无用首尾括号的StringBuffer类型
    120      */
    121     private static StringBuffer deleteHeadAndTail(StringBuffer expression){
    122         boolean tag3 = expression.charAt(0)=='(' && expression.charAt(expression.length()-1)==')';
    123         // 0下标和尾下标为括号
    124         if(tag3){
    125             StringBuffer temp = new StringBuffer(expression);
    126             temp.deleteCharAt(0);
    127             temp.deleteCharAt(temp.length()-1);
    128             boolean judge1 = !temp.toString().contains("(") && !temp.toString().contains(")");
    129             boolean judge2 = temp.toString().indexOf("(")<temp.toString().indexOf(")");
    130             // 如果删除完首尾的括号后没有括号了,为了预防多个括号都在首尾
    131             if(judge1){
    132                 expression = temp;
    133             }
    134             // 删除完首尾括号还有括号,并且剩下的括号满足左括号在右括号前
    135             else if(judge2){
    136                 expression = temp;
    137             }
    138         }
    139         return expression;
    140     }
    141 
    142     /**
    143      *   在数字、括号、运算符周围增加空格
    144      * @param expression 表达式的StringBuffer类型
    145      */
    146     private static void addBlank(StringBuffer expression) {
    147         String str = "+-×÷()";
    148         for (int i = 0; i < expression.length(); i++) {
    149             boolean tag = str.contains(String.valueOf(expression.charAt(i)));
    150             // 找到运算符或者括号
    151             if(tag){
    152                 // 不是在末尾,那么在后面1位插入空格
    153                 if(i != expression.length() - 1) {
    154                     expression.insert(++i, " ");
    155                 }
    156             }
    157             // 不是运算符,就只能是数字
    158             else{
    159                 // 右移
    160                 i = rightMove(expression,i);
    161                 // 不是在末尾,那么在后面1位插入空格
    162                 if(i != expression.length() - 1) {
    163                     expression.insert(++i, " ");
    164                 }
    165             }
    166         }
    167     }
    168 
    169     /**
    170      *   添加左括号的移位, 多位数需要移动到数的最左边添加括号
    171      * @param expression 表达式的StringBuffer类型
    172      * @param leftIndex  待定左括号的下标
    173      * @return  返回应该插入左括号的下标
    174      */
    175     private static int leftMove(StringBuffer expression,int leftIndex) {
    176         while (expression.charAt(leftIndex) >= 48 && expression.charAt(leftIndex) <= 57) {
    177             // 下标-1 左移
    178             if(leftIndex != 0){
    179                 leftIndex -= 1;
    180             }
    181             // 数字最左端到开头
    182             else{
    183                 return 0;
    184             }
    185         }
    186         // 跳出循环,找到第一个非数字的下标,+1回去最后一位数字的下标
    187         return leftIndex+1;
    188     }
    189 
    190     /**
    191      *   添加右括号的移位,多位数需要移动到数的最右边添加括号
    192      * @param expression 表达式的StringBuffer类型
    193      * @param rightIndex 待定右括号的下标
    194      * @return 返回应该插入右括号的下标
    195      */
    196     private static int rightMove(StringBuffer expression,int rightIndex){
    197         while(expression.charAt(rightIndex) >= 48 && expression.charAt(rightIndex) <= 57){
    198             // 下标+1 右移
    199             if(rightIndex != expression.length() - 1) {
    200                 rightIndex += 1;
    201             }
    202             // 数字最右端到末尾
    203             else{
    204                 return expression.length()-1;
    205             }
    206         }
    207         // 跳出循环,找到第一个非数字的下标,-1回去最后一位数字的下标
    208         return rightIndex-1;
    209     }
    210 
    211     /**
    212      *    加括号并对括号进行正确判断
    213      * @param expression 表达式的StringBuffer类型
    214      * @return 增添了正确括号的表达式的StringBuffer类型
    215      */
    216     private static StringBuffer addBrackets(StringBuffer expression){
    217         int i = 0;
    218         int leftIndex;
    219         Random r = new Random();
    220         // i用来控制括号的个数,且固定加入两个括号
    221         while(i < 2) {
    222             // 挑选数字,在左边加括号
    223             while (true) {
    224                 // 随机下标
    225                 leftIndex = r.nextInt(expression.length());
    226                 // 是数字,就在数字左边加 (
    227                 boolean tag1 = expression.charAt(leftIndex) >= 48 && expression.charAt(leftIndex) <= 57;
    228                 leftIndex = leftMove(expression,leftIndex);
    229                 // 插入(
    230                 if (tag1) {
    231                     expression.insert(leftIndex, "(");
    232                     break;
    233                 }
    234             }
    235             // 挑选数字,在右边加括号
    236             while (true) {
    237                 //  相匹配的右括号 ) , 必须在左括号 ( 右边,这个随机数能满足
    238                 int rightIndex = r.nextInt(expression.length() - leftIndex) + leftIndex;
    239                 boolean flag = true;
    240                 //  index>t ,是为了保证 ) 在 ( 右边, 由于在原数字左边 插入了( ,那么该数字的下标比原来多 1
    241                 boolean tag2 = expression.charAt(rightIndex) >= 48 && expression.charAt(rightIndex) <= 57 && rightIndex > leftIndex;
    242                 if (tag2) {
    243                     // 下标右移到数字最右端
    244                     rightIndex = rightMove(expression,rightIndex);
    245                     // 最左和最右不会出现“第二个括号中 ( 左边是 ( 同时 即将插入的 ) 的右边也是 )”的情况
    246                     if(rightIndex != expression.length() - 1 && leftIndex != 0) {
    247                         //  如果第二个括号中 ( 左边是 ( 同时 即将插入的 ) 的右边也是 ) 那么,不插入,且删除第二个 (
    248                         flag = expression.charAt(rightIndex + 1) != ')' || expression.charAt(leftIndex - 1) != '(';
    249                     }
    250                     // 不属于“第二个括号中 ( 左边是 ( 同时 即将插入的 ) 的右边也是 )”的情况
    251                     if (flag) {
    252                         //  插入时index+1, ) 放入数字右边
    253                         expression.insert(rightIndex + 1, ")");
    254                         break;
    255                     } else {
    256                         //  遇到了 ((1+2))+3 类似的情况
    257                         expression.deleteCharAt(leftIndex);
    258                         break;
    259                     }
    260                 }
    261             }
    262             //  排除首尾括号无用的情况
    263             expression = deleteHeadAndTail(expression);
    264             i++;
    265         }
    266         // 除去(112)类似的情况
    267         standard(expression);
    268         // 增加空格
    269         addBlank(expression);
    270         return expression;
    271     }
    272 }
    createCorrectExpression
  • CorrectAnswer:用于获取表达式正确答案的工具类

      1 /**
      2  * @author Mazin
      3  * 答案
      4  */
      5   6 public class CorrectAnswer {
      7   8     /**
      9      *   配合正确输出的算术表达式 并 做出正确答案
     10      * @param correctExpression 经过筛选过后的表达式
     11      * @return 返回表达式的结果
     12      */
     13     public static String answer(String correctExpression) {
     14         // 准备接受正确的结果
     15         String s;
     16         // 表达式的StringBuffer类型
     17         StringBuffer temp = new StringBuffer();
     18         // 运算符优先级数组
     19         String[] charpriority;
     20         // 计算主体 str 数组
     21         String[] str = correctExpression.split(" ");
     22         for (int i = 0; i < str.length; i++) {
     23             temp.append(str[i]);
     24         }
     25         //  得到优先级
     26         charpriority = priority(temp);
     27         //  得到charpriority的实际长度len
     28         int len = 0;
     29         for(int j = 0; j < charpriority.length && charpriority[j] != null; j++){
     30                 len++;
     31         }
     32         // 除去括号的 str 数组, 并调整优先级的下标
     33         for(int i = 0; i < str.length;){
     34             // 遇到括号,准备删除
     35             if("(".equals(str[i]) || ")".equals(str[i])){
     36                 // 将括号后面的数据全部前移一位
     37                 for(int j = i; j < str.length-1; j++) {
     38                     str[j] = str[j + 1];
     39                 }
     40                 // 需要调整所有的运算符
     41                 for(int j = 0; j < len; j++){
     42                     // 取出 运算符在 str 数组的下标
     43                     int n = Integer.parseInt(charpriority[j]);
     44                     // 运算符在括号后面才需要 -1 前移
     45                     if(n > i){
     46                         charpriority[j] = String.valueOf(--n);
     47                     }
     48                 }
     49             }else{
     50                 i++;
     51             }
     52         }
     53         //    按照运算符的顺序. 一个一个传入计算 , 在函数里计算
     54         s = adjust(str,charpriority,len);
     55         return s;
     56     }
     57  58     /**
     59      * 调整表达式,计算算法思想体现
     60      * @param str 表达式的String数组,主要在这里计算
     61      * @param charpriority 运算符的优先级String数组
     62      * @param len 运算符优先级数组的实际长度
     63      * @return 返回运算结果
     64      */
     65     private static String adjust(String[] str, String[] charpriority,int len) {
     66         String s = "";
     67         int oldIndex = 0;
     68         // count是计量 charpriority数组的实际长度
     69         for(int count = 0; count < len; count++) {
     70             //  取出 运算符 在 str 数组的下标
     71             int i = Integer.parseInt(charpriority[count]);
     72             //  将第1、2次的累计结果放在 上一次运算符 最靠近下一个运算符 的位置
     73             if (count >= 1) {
     74                 if (i > oldIndex) {
     75                     str[oldIndex + 1] = s;
     76                 } else {
     77                     str[oldIndex - 1] = s;
     78                 }
     79             }
     80             // 如果有3个运算符,那么最后一个,就强制把第二次的结果放到最后一个运算符的临位(左或右)
     81             if(count == 2){
     82                 if (i > oldIndex) {
     83                     str[i - 1] = s;
     84                 } else {
     85                     str[i + 1] = s;
     86                 }
     87             }
     88             //  得到运算符(String)的 char 类型
     89             char[] c = str[i].toCharArray();
     90             // 做计算前的准备,并进入下一层去计算
     91             s = beforeCaculate(str[i - 1],str[i + 1],c[0]);
     92             // 记录上一次运算符的位置
     93             oldIndex = i;
     94         }
     95         return s;
     96     }
     97  98     /**
     99      *   真分数数值计算, 转化为统一形式
    100      * @param a 参与运算的数字1的String形式
    101      * @param b 参与运算的数字2的String形式
    102      * @param c 运算符的字符形式
    103      * @return 返回运算结果
    104      */
    105     private static String beforeCaculate(String a,String b,char c) {
    106         String[] separate1,separate2,temp;
    107         int[] num1 = new int[3];
    108         int[] num2 = new int[3];
    109         // 变为统一形式, 要求数字必须为 "整数'分子/分母”形式
    110         if(!a.contains("/")){
    111             a = a + "\'0/1";
    112         }else if(!a.contains("'")){
    113             a = 0 + "'" + a;
    114         }
    115         if(!b.contains("/")){
    116             b = b + "\'0/1";
    117         }else if(!b.contains("'")){
    118             b = 0 + "'" + b;
    119         }
    120         //  提取 字符 a ,用separate1数组
    121         separate1 = a.split("'");
    122         temp = separate1[1].split("/");
    123         num1[0] = Integer.parseInt(separate1[0]);
    124         num1[1] = Integer.parseInt(temp[0]);
    125         num1[2] = Integer.parseInt(temp[1]);
    126         // 提取 字符 b ,用separate2数组
    127         separate2 = b.split("'");
    128         temp = separate2[1].split("/");
    129         num2[0] = Integer.parseInt(separate2[0]);
    130         num2[1] = Integer.parseInt(temp[0]);
    131         num2[2] = Integer.parseInt(temp[1]);
    132         // 计算前准备完成,计算
    133         String s = caculate(num1,num2,c);
    134         return s;
    135     }
    136 137     /**
    138      *     选择运算符,通分计算
    139      * @param num1 运算的第一个数
    140      * @param num2 运算的第二个数
    141      * @param c  运算符的字符类型
    142      * @return 返回num1和num2的运算结果
    143      */
    144     private static String caculate(int[] num1,int[] num2,char c){
    145         String s = "";
    146         // up:分子,down:分母
    147         int up1,down1,up2,down2;
    148         up1 = num1[0] * num1[2] + num1[1];
    149         down1 = num1[2];
    150         up2 = num2[0] * num2[2] + num2[1];
    151         down2 = num2[2];
    152         // 通分结果,result[0]放分子,result[1]放分母
    153         int[] result = new int[2];
    154         // 简单通分,直接分子分母相乘
    155         up1 = up1 * down2;
    156         up2 = up2 * down1;
    157         // 通分后分母
    158         result[1] = down1 * down2;
    159         // 选择运算符
    160         switch (c) {
    161             case '+':
    162                 result[0] = up1+up2;
    163                 break;
    164             case '-':
    165                 result[0] = up1-up2;
    166                 break;
    167             case '÷':
    168                 result[0] = up1 * result[1];
    169                 result[1] = up2 * result[1];
    170                 break;
    171             case '×':
    172                 result[0] = up1*up2;
    173                 result[1] = result[1] * result[1];
    174                 break;
    175             default:
    176         }
    177         // 整数部分
    178         int integer = result[0] / result[1];
    179         // 分子部分
    180         int fraction = result[0] % result[1];
    181         // 因为最后需要除以公约数,防止除以0,因为0和其他数的最大公约为0
    182         if(fraction != 0) {
    183             // 求最大公约数
    184             int common = f(fraction, result[1]);
    185             //  约分
    186             fraction = fraction / common;
    187             result[1] = result[1] / common;
    188         }
    189         // 选择输出的形式
    190         if (integer != 0) {
    191             if(fraction != 0) {
    192                 s = integer + "'" + fraction + "/" + result[1];
    193             }else{
    194                 s = integer + "";
    195             }
    196         } else {
    197             if(fraction != 0) {
    198                 s = fraction + "/" + result[1];
    199             }else{
    200                 s = "0";
    201             }
    202         }
    203         return s;
    204     }
    205 206     /**
    207      *  辗转相除 求最大公约数,down大 up小
    208      * @param up 分子
    209      * @param down 分母
    210      * @return 返回up和down的最大公约数
    211      */
    212     private static int f(int up,int down){
    213         int temp;
    214         while(true) {
    215             temp = down % up;
    216             if (temp == 0) {
    217                 return up;
    218             } else {
    219                 down = up;
    220                 up = temp;
    221             }
    222         }
    223     }
    224 225     /**
    226      *      计算符号优先级
    227      * @param temp 表达式的StringBuffer类型
    228      * @return 返回排好运算符优先级的String[]数组,[0]优先级最高
    229      */
    230     private static String[] priority(StringBuffer temp) {
    231         String[] newstr;
    232         StringBuffer t = new StringBuffer(temp);
    233         //  4个括号的下标数组,初始化-1
    234         // blankets 记录 表达式StringBuffer类型的 括号下标,[0][1]记录外层括号,[2][3]记录内层括号
    235         int[] blankets = {-1, -1, -1, -1};
    236         // real 记录 表达式String[]类型的 括号下标,(主要)[0][1]记录外层括号,[2][3]记录内层括号
    237         int[] real = {-1,-1,-1,-1};
    238         blankets[0] = t.indexOf("(");
    239         if(blankets[0] != -1) {
    240             t.deleteCharAt(blankets[0]);
    241             if (t.indexOf("(") == -1) {
    242                 //  仅有一个括号,且被删除了 (
    243                 blankets[1] = t.indexOf(")") + 1;
    244             } else if (t.indexOf("(") > t.indexOf(")")) {
    245                 blankets[1] = t.indexOf(")") + 1;
    246                 //  被删除了 (
    247                 blankets[2] = t.indexOf("(") + 1;
    248                 blankets[3] = t.lastIndexOf(")") + 1;
    249             } else {
    250                 blankets[1] = t.lastIndexOf(")") + 1;
    251                 blankets[2] = t.indexOf("(") + 1;
    252                 blankets[3] = t.indexOf(")") + 1;
    253             }
    254         }
    255         //  real先拿到blankets的值,再做更新
    256         for(int i = 0; i < 4; i++){
    257             real[i] = blankets[i];
    258         }
    259         // 更新real为 str 数组的括号下标,一共4个括号
    260         for(int j = 0; j < 4; j++) {
    261             // flag 用于多位数的 计数,如 123 应该记为 1。
    262             boolean flag = true;
    263             //  count是记录实际 str 数组的括号下标,-1表示不存在
    264             int count = -1;
    265             if(real[j] != -1) {
    266                 x:
    267                 // 在遇到 指定 的括号前,计数 数字、运算符、其他括号
    268                 for (int i = 0; i <= real[j]; i++) {
    269                     boolean tag1 = (temp.charAt(i) >= 48 && temp.charAt(i) <= 57);
    270                     boolean tag2 = temp.charAt(i) == '×' || temp.charAt(i) == '-' || temp.charAt(i) == '+' || temp.charAt(i) == '÷';
    271                     // 遇到数字
    272                     if (tag1 && flag) {
    273                         count++;
    274                         flag = false;
    275                     }
    276                     // 遇到运算符
    277                     if (tag2) {
    278                         count++;
    279                         flag = true;
    280                     }
    281                     // 遇到括号,是否为 指定 括号待定
    282                     if (!tag1 && !tag2) {
    283                         // 到达末尾,则为 指定 括号
    284                         if (i == real[j]) {
    285                             real[j] = ++count;
    286                             break x;
    287                         }
    288                         // 不是 指定 括号
    289                         else{
    290                             flag = true;
    291                             count++;
    292                         }
    293                     }
    294                 }
    295             }
    296         }
    297 298         //求表达式StringBuffer类型下的运算符和下标,不考虑优先级,直接从左到右读取运算符
    299         String[] oldstr = charAndIndex(temp);
    300         // 结合括号,运算符 整理得到 最终 按优先级排序的 运算符 的 下标
    301         newstr= sort(blankets, oldstr,temp,real);
    302         return newstr;
    303     }
    304 305     /**
    306      * 统计括号的情况,
    307      * @param blankets 表达式StringBuffer类型的 括号下标
    308      * @param oldstr  表达式 运算符及其下标的数组,下标在前,运算符在后
    309      * @param expression 表达式的StringBuffer类型
    310      * @param real  表达式String数组类型的 括号下标
    311      * @return  返回排好运算符优先级的String[]数组,[0]优先级最高,且只存下标,该下标针对表达式的String数组下标
    312      */
    313     private static String[] sort(int[] blankets, String[] oldstr,StringBuffer expression,int[] real) {
    314         String[] newstr = new String[6];
    315        //  将oldstr数组拷贝到 oldstring 数组
    316         String[] oldstring = new String[6];
    317         for(int i = 0 ; i < 6; i++){
    318             oldstring[i] = oldstr[i];
    319         }
    320         //  按照无括号的形式排序优先级得到all,其实只是为了方便找到未进行排序的运算符下标
    321         String[] all = blanketsJudge(oldstr);
    322         // all 和 oldstr 的区别:对整条表达式,all比较了优先级(“忽视”括号的情况),oldstr未比较优先级,只是从左到右记录
    323         // 无内层括号
    324         if (blankets[2] == -1) {
    325             //  无内层无外层括号
    326             if (blankets[0] == -1) {
    327                 // 无括号便是 all 的情况
    328                 newstr = all;
    329                 return newstr;
    330             } else {
    331                 String[] str;
    332                 //  截取出括号内的表达式,这也是为什么要blankets的原因
    333                 StringBuffer s = new StringBuffer(expression.substring(blankets[0]+1,blankets[1]));
    334                 // 求出括号内表达式的运算符和下标
    335                 str = charAndIndex(s);
    336                 //截取出来的运算符下标,这个下标是在str数组内的,所以少了括号的下标用real,要加上
    337                 int j = 0;
    338                 while(str[j] != null){
    339                     str[j] = String.valueOf(Integer.parseInt(str[j]) + real[0] + 1);
    340                     j += 2;
    341                 }
    342                 // 对括号内的运算符做优先级排序,输出运算符下标
    343                 newstr = blanketsJudge(str);
    344                 // 算str的实际长度, 其实也就是 newstr现有长度的两倍
    345                 int len = 0;
    346                 for(j = 0; str[j] != null; j++){
    347                     len++;
    348                 }
    349                 len /= 2;
    350                 // 找到,未进行排序的 运算符下标, 而all已经排序了,从头找到未进行排序的下标,按顺序添加至newstr末尾就行
    351                 x:
    352                 for (int i = 0; i < all.length && all[i] != null;i++) {
    353                     for (int x = 0; newstr[x] != null; x++) {
    354                         if (newstr[x].equals(all[i])) {
    355                             continue x;
    356                         }
    357                     }
    358                     newstr[len++] = all[i];
    359                 }
    360                 return newstr;
    361             }
    362         }else{
    363             //2个括号,一定是3个运算符
    364             String[] temp = new String[6];
    365             int j = 0, p;
    366             String[] str1;
    367             String[] str2;
    368             //  双括号嵌套
    369         if(blankets[1] > blankets[2]) {
    370             // 截取括号内的StringBuffer表达式
    371             StringBuffer s1 = new StringBuffer(expression.substring(blankets[0] + 1, blankets[1]));
    372             StringBuffer s2 = new StringBuffer(expression.substring(blankets[2] + 1, blankets[3]));
    373             // 求出括号内表达式的运算符和下标
    374             str1 = charAndIndex(s1);
    375             str2 = charAndIndex(s2);
    376             //  截取出来的运算符下标,这个下标是在str数组内的,所以少了括号的下标用real,要加上
    377             while (str2[j] != null) {
    378                 str2[j] = String.valueOf(Integer.parseInt(str2[j]) + real[2] + 1);
    379                 j += 2;
    380             }
    381             j = 0;
    382             while (str1[j] != null) {
    383                 str1[j] = String.valueOf(Integer.parseInt(str1[j]) + real[0] + 1);
    384                 j += 2;
    385             }
    386             //str2是必定是最内层括号内的 下标和运算符
    387             newstr[0] = str2[0];
    388             // 比较外层括号里的2个运算符,找到跟内层括号不同的运算符下标
    389             if (str1[0].equals(str2[0])) {
    390                 newstr[1] = str1[2];
    391             } else {
    392                 newstr[1] = str1[0];
    393             }
    394             x:
    395             // 第3个运算符需要跟 all 比较,少的那个直接加到 newstr 末尾
    396             for (p = 0; p < all.length; p++) {
    397                 for (int x = 0; newstr[x] != null; x++) {
    398                     if (newstr[x].equals(all[p])) {
    399                         continue x;
    400                     }
    401                 }
    402                 newstr[2] = all[p];
    403                 break x;
    404             }
    405             return newstr;
    406         }
    407         // 平行双括号(1+2)-(3+4), 那么,必定是第一个运算符,第三个运算符,第二个运算符这样的顺序
    408         else{
    409                 newstr[0] = oldstring[0];
    410                 newstr[1] = oldstring[4];
    411                 newstr[2] = oldstring[2];
    412                 return newstr;
    413             }
    414         }
    415     }
    416 417     /**
    418      * 输出  str数组内运算符 按优先级排序好的顺序, 记录下标
    419      * @param str 需要作运算符优先级判断的数组
    420      * @return 返回str数组内运算符的优先级顺序
    421      */
    422     private static String[] blanketsJudge(String[] str){
    423         boolean tag1,tag2,flag = false,endtag = true;
    424         // temp用来记录 +- 符号的 下标
    425         int temp = -1,j = 0;
    426         for(int i = 1; i < str.length; i+=2) {
    427             //  拿出下标对应的运算符
    428             tag1 = "+".equals(str[i]) || "-".equals(str[i]);
    429             tag2 = "×".equals(str[i]) || "÷".equals(str[i]);
    430             //  遇到 +-
    431             if (tag1) {
    432                 // flag 表示先遇到+-在遇到×÷,那么就需要调整优先级
    433                 flag = true;
    434                 // temp 记录 +- 下标
    435                 temp = i;
    436             }
    437             // 遇到×÷
    438             if (tag2) {
    439                 // flag为true,就是需要交换了(+-在×÷之前),到时候直接从数组开头到末尾读取
    440                 if (flag) {
    441                     // 把下标和运算符都交换
    442                     swap(str, i, temp);
    443                     swap(str, i - 1, temp - 1);
    444                     // 有交换,那么不结束
    445                     endtag = false;
    446                     // temp 跟进 +- 号
    447                     temp = i;
    448                 }
    449                 // 如果前面不做交换,那么有结束的前提
    450                 else {
    451                     endtag = true;
    452                 }
    453             }
    454             // 结束的标志有 endtag,且得到表达式末尾
    455             if (!endtag && i == str.length - 1) {
    456                 // 到达末尾,但不能结束,那么,初始化数据,重新遍历,i=-1,是为了i+=2能变成1
    457                 flag = false;
    458                 temp = -1;
    459                 i = -1;
    460             }
    461             // endtag为true表示可以结束了,如果此时到末尾,直接结束
    462             else if (endtag && i == str.length - 1) {
    463                 break;
    464             }
    465             // 处理下一个运算符
    466         }
    467         String[] newstr = new String[6];
    468         //   输出排序后, 运算符的下标,偶数下标
    469         for(int index = 0;index < str.length; index+=2){
    470             newstr[j++] = str[index];
    471         }
    472         return newstr;
    473     }
    474 475 476     private static void swap(String[] oldstr,int a,int b){
    477         String temp;
    478         temp = oldstr[a];
    479         oldstr[a] = oldstr[b];
    480         oldstr[b] = temp;
    481     }
    482 483     /**
    484      *   求运算符和下标
    485      * @param temp 表达式的StringBuffer类型
    486      * @return 返回运算符及其下标的数组,下标在前,运算符在后
    487      */
    488     private static String[] charAndIndex(StringBuffer temp){
    489         int count = -1 ;
    490         // flag 计数 运算符前的 个数
    491         boolean flag = true;
    492         String[] oldstr = new String[6];
    493         for (int i = 0, j = 0; i < temp.length(); i++) {
    494             // 数字
    495             boolean tag1 = (temp.charAt(i) >= 48 && temp.charAt(i) <= 57);
    496             // 括号或=
    497             boolean tag2 = temp.charAt(i) == '(' || temp.charAt(i) == ')' || temp.charAt(i) == '=';
    498             // 遇到数字,且flag为true,flag是保证在多位数情况下正确计数
    499             if(tag1 && flag){
    500                 count++;
    501                 flag = false;
    502             }
    503             // 遇到括号或=
    504             if(tag2){
    505                 count++;
    506                 flag = true;
    507             }
    508             // 遇到运算符
    509             if (!tag1 && !tag2) {
    510                 // 运算符的下标
    511                 oldstr[j++] = String.valueOf(++count);
    512                 // 运算符
    513                 oldstr[j++] = String.valueOf(temp.charAt(i));
    514                 flag = true;
    515             }
    516         }
    517         return oldstr;
    518     }
    519 520 }
    answer
  • Accuracy:用于获取用户答案并判断的工具类

     1 /**
     2  * @author Mazin
     3  * 正确率Result
     4  */
     5 public class Accuracy{
     6  7     /**
     8      *  输出正确率到文件中,需要比较userAnswers和Answers
     9      * @param userAnswers 用户答案
    10      * @param grade 正确率文件
    11      * @throws Exception
    12      */
    13     public static void result(String[] userAnswers,File grade) throws Exception {
    14         //  拿到正确答案集,和用户答案做比较
    15         // 正确题目数和错误题目数
    16         int correctNum = 0;
    17         int wrongNum = 0;
    18         // 正确行输出
    19         String correctIndex = "";
    20         // 错误行输出
    21         String wrongIndex = "";
    22         // 得到标准答案数组
    23         String[] answers = CreateCorrectExpression.getAnswers();
    24         String[] write = new String[2];
    25         for (int i = 0; i < answers.length && answers[i] != null; i++) {
    26             // 相同下标,用户答案和标准答案一样便是正确
    27             if (answers[i].equals(userAnswers[i])) {
    28                 correctNum++;
    29                 //  +1 是为了达到正确的题目序号,保存所有正确的题目序号
    30                 correctIndex += i + 1 + " ,";
    31             } else {
    32                 wrongNum++;
    33                 wrongIndex += i + 1 + " ,";
    34             }
    35         }
    36         StringBuffer s1 = new StringBuffer(correctIndex);
    37         StringBuffer s2 = new StringBuffer(wrongIndex);
    38         // 正确数为0,就不加上后面的括号()
    39         if (correctNum != 0){
    40             s1.insert(0, "( ");
    41             //  取代末尾的  , 变为 )
    42             s1.replace(s1.length()-1,s1.length(),")");
    43         }
    44         if(wrongNum != 0) {
    45             s2.insert(0, "(");
    46             s2.replace(s2.length()-1,s2.length(),")");
    47         }
    48         correctIndex = s1.toString();
    49         wrongIndex = s2.toString();
    50         // 拼接
    51         write[0] = "Correct : " + correctNum + " " + correctIndex;
    52         write[1] = "Wrong : " + wrongNum + " " + wrongIndex;
    53         write(grade,write);
    54     }
    55 56     /**
    57      *  专门用于Grade文件的写
    58      * @param fileName Grade.txt
    59      * @param content 写入Grade.txt文件的内容
    60      * @throws Exception
    61      */
    62     private static void write(File fileName,String[] content) throws Exception{
    63         FileWriter fw = new FileWriter(fileName);
    64         for(int i = 0; i < content.length; i++) {
    65             fw.write(content[i]);
    66             fw.write("\n");
    67         }
    68         fw.close();
    69     }
    70 71 }
  • IsRepeatExpression:用于判断是否有重复表达式工具类

     1 public class IsRepeatExpression {
     2     private Ruler ruler = new Ruler();
     3  4     /**
     5      *
     6      * 完成从后缀表达式构造查重表达式的过程(主要是运用堆栈的做法,基本和计算后缀表达式一样的做法).
     7      * 以后缀的长度循环,遇到数字压栈,遇到字符连续出栈两个数字字符,出栈后将“#”压入数字栈,这个字符只起占位的作用,
     8      * 方便代码编写.循环结束后即可得到查重表达式
     9      *
    10      */
    11 12     public void getisrepeatExpressionArray(String[] targetArray, Expression expression){
    13         String topStr=  null;//栈顶的字符串
    14         int isrepeatArrayLength = 0;//用于表示查重表达式的长度
    15         Stack<String> stack = new Stack<String>();
    16         int lenth = ruler.getTrueLength(targetArray);//获得后缀表达式的真实长度
    17         String tempStr = null;
    18         for (int i = 0; i < lenth; i++) {// 字符串的长度超过一,代表这是数字则压栈
    19             if (targetArray[i].length() > 1||targetArray[i].equals("#")) {
    20                 stack.push(targetArray[i]);
    21             } else {// 代表这是运算符
    22                 expression.getisrepeatExprssion()[isrepeatArrayLength++] = targetArray[i];//加入到查重表达式中
    23                 topStr = stack.pop();
    24                 if(!topStr.equals("#")){
    25                     expression.getisrepeatExprssion()[isrepeatArrayLength++] = topStr;//加入到查重表达式中
    26                 }
    27                 topStr = stack.pop();
    28                 if(!topStr.equals("#")){
    29                     expression.getisrepeatExprssion()[isrepeatArrayLength++] = topStr;//加入到查重表达式中
    30                 }
    31                 stack.push("#");
    32             }
    33         }
    34     }
    35 36     /**
    37      *
    38      *
    39      * @param exp1 要查重的表达式一
    40      * @param exp2 要查重的表达式二
    41      * @return 这两表达式是否重复
    42      */
    43     public Boolean isrepeatExression(Expression exp1, Expression exp2){
    44         String [] isrepeatStrArray1 = exp1.getisrepeatExprssion();
    45         String [] isrepeatStrArray2 = exp2.getisrepeatExprssion();
    46         int arrayLenth1 = ruler.getTrueLength(isrepeatStrArray1); //获得表达式一查重数组的真实长度
    47         int arrayLenth2 = ruler.getTrueLength(isrepeatStrArray2);//获得表达式二查重数组的真实长度
    48         if(arrayLenth1!=arrayLenth2) return false;// 若数组长度不相等,则表达式一定不同.
    49         if(ruler.arrayToString(isrepeatStrArray1).equals(ruler.arrayToString(isrepeatStrArray2))) return true; //如果查重数组完全一致则是为重复数组.
    50         if(isrepeatStrArray1[0].equals("+")||isrepeatStrArray1[0].equals("*")){//只有加或乘的情况才可能出现 交换左右操作数当做重复的表达式
    51             String temp = isrepeatStrArray1[1]; //交换首个符号之后的两个数字
    52             isrepeatStrArray1[1] = isrepeatStrArray1[2];
    53             isrepeatStrArray1[2] = temp;
    54         }
    55         //若交换后相等则也为重复.
    56         if(ruler.arrayToString(isrepeatStrArray1).equals(ruler.arrayToString(isrepeatStrArray2))) return true; //如果查重数组完全一致则是为重复数组.
    57         return false;
    58     }
    59 }
  • Screen:用于筛选不符合规则的工具类

      1 /**
      2  *
      3  *核心思想用递归方法解决算术表达式的分解、判断
      4  * 注释中基础表达式是指一个运算符两个运算数子,例如:"1 - 2"
      5  *@description: 筛选类,包含筛选表达式所使用的方法
      6  *@author: MDL
      7  *@time:  2020.3.31
      8  *
      9  */
     10 public class Screen {
     11  12     static Screen screen = new Screen();
     13  14     /**
     15      * 粗略筛选
     16      * @param exps 算术表达式字符串,数字和符号用空格隔开
     17      * @return 返回字符串,如果为"false"则表明算术表达式不符合
     18      */
     19     public String roughScreen(String exps){
     20         String result = "";//存放结果字符串
     21         String[] exps_array = screen.splitExpression(exps);//得到分隔后的字符串数组
     22         if (exps_array.length == 1){//字符串数组只有一位表示为数字
     23             result = exps;//直接返回值
     24         }else if (exps_array.length == 3){//一个基础的算术表达式,例如:"1 - 2"
     25             if (true == baseExpression(exps)){//判断是否符合基础规则
     26                 result = screen.espressionResult(exps);//计算返回值
     27             }else{
     28                 result = "false";//不符合基本规则返回"false"用于递归
     29             }
     30         }else if(exps.equals("false")){//递归时用于判断
     31             result = "false";
     32         }else {
     33             String left_exps = "";//左算术式
     34             String left_result = "";//左算术式结果
     35             String right_exps = "";//右算术式
     36             String right_result = "";//右算术结果
     37             int[] adr_array = screen.operator(exps);//获取算术表达式的运算符优先级,存放的为所在原字符串数组下标
     38             int adr = adr_array[adr_array.length-1];//获取当前算术运算符
     39             for (int i = 0;i <= adr-1;i++){//以当前算术运算符向左拼接表达式获取左表达式
     40                 left_exps += exps_array[i] + " ";
     41             }
     42             left_exps = screen.deleteBracket(left_exps);//去括号
     43             for (int j = adr+1;j <= exps_array.length-1;j++){//以当前算术运算符向右拼接表达式获取右表达式
     44                 right_exps += exps_array[j] + " ";
     45             }
     46             right_exps = screen.deleteBracket(right_exps);
     47             left_result = screen.roughScreen(left_exps);//左表达式递归
     48             right_result = screen.roughScreen(right_exps);//右表达式递归
     49             if (!left_result.equals("false") && !right_result.equals("false")){//左右子表达式都符合要求
     50                 String new_exps;//把左结果和右结果以及运算符结合成新的运算式
     51                 new_exps = left_result + " " + exps_array[adr]+ " "+ right_result;//生成新的基本表达式
     52                 if (true == baseExpression(new_exps)) {//新的基本表达式是否符合基础要求
     53                     result = screen.espressionResult(new_exps);//计算结果
     54                 }else{
     55                     result = "false";//新基础表达式不符合
     56                 }
     57             }else {
     58                 result = "false";
     59             }
     60         }
     61         return result;
     62     }
     63  64     /**
     65      * 去除算术表达式括号
     66      * @param exps 算术表达式字符串,数字和符号用空格隔开
     67      * @return 返回去掉首尾括号的算术表达式字符串
     68      */
     69     public String deleteBracket(String exps){
     70         String[] exps_array = screen.splitExpression(exps);//得到分隔后的字符串数组
     71         //首尾是否右括号
     72         if (exps_array[0].equals("(") && exps_array[exps_array.length-1].equals(")")){
     73             return exps.substring(2,exps.length()-2);
     74         }else {
     75             return exps;
     76         }
     77     }
     78  79     /**
     80      * 粗略计算基本表达式结果,不作真分数、分数运算
     81      * @param exps 算术表达式字符串,数字和符号用空格隔开
     82      * @return 返回数值用字符串表示
     83      */
     84     public String espressionResult(String exps){
     85         String result_str;//运算结果
     86         String[] exps_array = screen.splitExpression(exps);//得到分隔后的字符串数组
     87         //默认左右运算数用double运算
     88         double left = Double.parseDouble(exps_array[0]);
     89         double right = Double.parseDouble(exps_array[2]);
     90         double result;
     91         switch (exps_array[1]){
     92             case "+":
     93                 result = left + right;
     94                 result_str = String.valueOf(result);
     95                 break;
     96             case "-":
     97                 result = left - right;
     98                 break;
     99             case "×":
    100                 result = left * right;
    101                 break;
    102             case "÷":
    103                 result = left / right;
    104                 break;
    105             default:
    106                 result = -1;
    107         }
    108         result_str = String.valueOf(result)+ " ";//结果尾处加一空格,方便字符串分解
    109         return result_str;
    110     }
    111 112     /**
    113      * 获取算术表达式运算符的优先级
    114      * 核心思想遍历一次算术表达式,依次将括号内的运算符、乘除、加减所在数组位置放入各自优先级数组位置,记录的是运算符的地址
    115      * @param total_exps 算术表达式字符串,数字和符号用空格隔开
    116      * @return 返回数组记录运算符优先级所在地址
    117      */
    118     public int[] operator(String total_exps){
    119         String[] exps_array = screen.splitExpression(total_exps);//得到分隔后的字符串数组
    120         int[] adr_add = new int[3];//记录加减号地址的数组
    121         int[] adr_minus = new int[3];//记录乘除号地址的数组
    122         int[] adr_bracket = new int[3];//记录括号内运算符地址的数组
    123         int count = 0;//运算符数量统计
    124         int adr_bracket_i = 0;//括号优先级数组下标
    125         int adr_add_i = 0;//加减号优先级数组下标
    126         int adr_minus_i =0;//乘除号优先级数组下标
    127         int adr_i = 0;//总优先级数组下标
    128         int j;
    129         for (int i = 0;i < exps_array.length; ++i){
    130             if (exps_array[i].equals("(")){//括号优先级
    131                 if (exps_array[i+1].equals("(")){//例如:((1+2)+3)
    132                     adr_bracket[adr_bracket_i] = i + 3;
    133                     adr_bracket[adr_bracket_i+1] = i + 6;
    134                     adr_bracket_i = adr_bracket_i +2;
    135                     i = i + 8;
    136                     count+=2;
    137                 }else if (exps_array[i+3].equals("(")){//例如:(1+(1+2))
    138                     adr_bracket[adr_bracket_i] = i + 5;
    139                     adr_bracket[adr_bracket_i+1] = i + 2;
    140                     adr_bracket_i = adr_bracket_i +2;
    141                     i = i + 8;
    142                     count+=2;
    143                 }else if (exps_array[i+4].equals(")")){//例如:(1+2)+3
    144                     adr_bracket[adr_bracket_i] = i + 2;//存放符号所在exps_array数组地址
    145                     adr_bracket_i++;
    146                     i = i + 4;
    147                     count++;
    148                 }else if (exps_array[i+6].equals(")")){//例如:(1+2+3)+4
    149                     if (exps_array[i+2].equals("×") || exps_array[i+2].equals("÷")){
    150                         adr_bracket[adr_bracket_i]= i + 2;
    151                         adr_bracket[adr_bracket_i+1] = i + 4;
    152                     }else{ // (exps_array[i+2].equals("+") || exps_array[i+2].equals("-"))
    153                         if (exps_array[i+4].equals("×") || exps_array[i+4].equals("÷")){
    154                             adr_bracket[adr_bracket_i]= i + 4;
    155                             adr_bracket[adr_bracket_i+1] = i + 2;
    156                         }else {
    157                             adr_bracket[adr_bracket_i]= i + 2;
    158                             adr_bracket[adr_bracket_i+1] = i + 4;
    159                         }
    160                     }
    161                     adr_bracket_i = adr_bracket_i + 2;
    162                     i = i + 6;
    163                     count+=2;
    164                 }
    165             }else if(exps_array[i].equals("×") || exps_array[i].equals("÷")){
    166                 adr_minus[adr_minus_i] = i;
    167                 adr_minus_i++;
    168                 count++;
    169             }else if (exps_array[i].equals("+") || exps_array[i].equals("-")){
    170                 adr_add[adr_add_i] = i;
    171                 adr_add_i++;
    172                 count++;
    173             }
    174         }
    175         int[] adr = new int[count];//总优先级数组
    176         for ( j = 0;adr_bracket_i != 0 && j <= adr_bracket_i-1;j++){//依次存入括号优先级运算符地址
    177             adr[adr_i] = adr_bracket[j];
    178             adr_i++;
    179         }
    180         for ( j = 0;adr_minus_i != 0 && j <= adr_minus_i-1;j++){//依次存入乘除号优先级运算符地址
    181             adr[adr_i] = adr_minus[j];
    182             adr_i++;
    183         }
    184         for ( j = 0;adr_add_i != 0 && j <= adr_add_i-1;j++){//依次存入加减号优先级运算符地址
    185             adr[adr_i] = adr_add[j];
    186             adr_i++;
    187         }
    188         return adr;
    189     }
    190 191     /**
    192      * 分割字符串,以空格为标志将运算数、运算符号、括号作为字符串存入字符串数组
    193      * @param exps 算术表达式字符串,数字和符号用空格隔开
    194      * @return 返回字符串数组
    195      */
    196     public String[] splitExpression(String exps){
    197         //以空格为标志
    198         String[] exps_array = exps.split(" +");
    199         return exps_array;
    200     }
    201 202     /**
    203      * 基础算术表达式判断,是否会出现负数结果,是否会出现除数为0
    204      * @param base_exps 基础算术表达式字符串,数字和符号用空格隔开
    205      * @return true:符合规则;false:不符合规则
    206      */
    207     public boolean baseExpression(String base_exps){
    208         String[] exps_array = screen.splitExpression(base_exps);//得到分隔后的字符串数组
    209         //表达式只有一个运算符的,例如"1 + 2"
    210         if (exps_array.length == 3){
    211             if (exps_array[1].equals("÷")){//运算符是除号
    212                 if(screen.zeroBehindDenominator(exps_array[2]) == true)//除数为0
    213                     return false;
    214             }else if(exps_array[1].equals("-")){//运算符号是减号
    215                 if (screen.negativeExps(exps_array[0],exps_array[2]) == true)//子运算式为负数结果
    216                     return false;
    217             }
    218             return true;
    219         }else {
    220             return false;
    221         }
    222     }
    223 224     /**
    225      * 判断除数是否为0
    226      * @param s 除数,字符串类型
    227      * @return true:除数为0;false:除数不为0
    228      */
    229     public boolean zeroBehindDenominator(String s){
    230         //字符串只有一位且为“0”;
    231         if (s.equals("0") || s.equals("0.0")){
    232             return true;
    233         }else {
    234             return false;
    235         }
    236     }
    237 238     /**
    239      * 基本运算式是否会出现负数
    240      * @param s1 被减数字符串
    241      * @param s2 减数字符串
    242      * @return true:运算结果为负数;false:运算结果不为负数
    243      */
    244     public boolean negativeExps(String s1,String s2){
    245         double num1,num2;
    246         num1 = Double.parseDouble(s1);
    247         num2 = Double.parseDouble(s2);
    248         //比较被减数与减数关系
    249         if (num1 < num2) {
    250             return true;
    251         }else {
    252             return false;
    253         }
    254     }
    255 }
    screen

5.3待开发功能…

  • 有关算术表达式生成范围,目前仍未实现生成分数或者真分数

  • 用户答案输入不能完全识别是否为分数,例如:3‘‘’‘’‘’‘’、/1、////、‘’‘’‘’程序也识别为答案

  • 有关筛选类和生成答案类相当一部分代码功能相同但是由于分工合作交流不当编写时产生冗余部分仍需改进

  • 关于判断算术表达式是否有重复功能,例如(1+2)*(3+4) 和(4+3)*(2+1)此类型表达式我们尚未想出办法解决,基于初步想法也只想出精确筛选表达式是否会有重复可以分三种情况进行,且筛选正确率逐步提升:

    1. 依据两式子答案相同、字符串长度相同、运算符计算优先级相同判断为两表达式等价:

    2. 将获取式子的优先级和运算数提取成查重表达式,例如: 原式为:3*(1+2) +5 其后缀为 1 2 + 3 * 5 +,依据加号和乘号两边数据可调换,判断传入两表达式的查重式是否相同判断是否等价

       1 public int getisrepeatFlag() {
       2         return isrepeatFlag;
       3     }
       4  5     public void setisrepeatFlag(int isrepeatFlag) {
       6         this.isrepeatFlag = isrepeatFlag;
       7     }
       8  9     public String[] getisrepeatExprssion() {
      10         return isrepeatExprssion;
      11     }
      12 13     public void setisrepeatExprssion(String[] isrepeatExprssion) {
      14         this.isrepeatExprssion = isrepeatExprssion;
      15     }
      16 //    public Expression(){
      17 //        operatorNumber = new String[5]; //为方便计算将所有数组初始化为空
      18 //        for(int i = 0;i<operatorNumber.length;i++){
      19 //            operatorNumber[i] = null;
      20 //        }
      21 //        operator = new String[6]; //为方便计算将所有数组初始化为空
      22 //        for(int i = 0;i<operator.length;i++){
      23 //            operator[i] = null;
      24 //        }
      25 //        isrepeatExprssion = new String[7];
      26 //        for(int i = 0;i<isrepeatExprssion.length;i++){
      27 //            isrepeatExprssion[i] = null;
      28 //        }
      29 //    }
    3. 而对于可执行交换律的运算符“*”和“+”,当出现三个运算符都是此两种时,仍未想出具体算法可以准确且高效判断是否等价

6.效能分析与测试运行

6.1效能分析与改进

数据类型消耗,char[] 、Object[]、String[] 、StringBuffer 类型用得最多

内存消耗,只有一条线程main

class占用cpu

 

 

由图可知,几乎都消耗在String.nextLine()输入上,特别是最后一步在控制台获取用户答案时,占用了后面几乎所有的资源

6.2用例测试

使用说明:

(1)点击运行程序,首先需要输入命令和两个数字,“-n ”命令后面加第一个数字num,表示控制生成的题目数量num、“-r ”命令后面加第二个数字,表示控制题目中出现的数字的数值范围 [ 0 , limits )。

(2)接着,会在控制台自动出现一条表达式,需要作完一道题后才能出现下一道,没法跳过题目做下一道题。所有的题目都保存在当前目录的Exercise.txt文件,标准答案在Answer.txt文件。

(3)做完题目后,选择是否进行校对答案。输入“ -e -a ”命令表示校对,校对的结果在当前目录下的Grade.txt文件;输入 “ exit ” 表示退出,虽然退出了,但是还是要记得校对答案哦。

  1. 校对答案例子(包括用户的一些错误输入处理)

     

     

     

     

  2. 不校对答案例子

     

  3. 支持生成1w条表达式

7.初尝团队合作

7.1小白入门…初步分工

    

  • 团队合作用在结对编程这件事我们确实是第一次接触,一开始只是草率地分配任务放羊式打码;后来准备收官的时候才发现有些需求实在没想出来,但是冷静思考这次作业的意义是什么:不在于你的功能有多高大上,我认为是可以体验软件开发中的必要流程;包括:

    • 重视PSP

    • 合理使用工具(单元测试、性能测试、版本控制软件等)

    • 协作开发(各自获取需求是否有考虑有重复的地方、每天的项目进度需要及时沟通跟进、必要的舍弃与加班等)

    以上种种,我们有一部分是后面几天才简单体验过,所以项目至此还会有许多问题;但同时也是经验,为我们下次做得更好

7.2软件版本控制入门学习

  • 版本控制确实是开发过程中非常必需的流程,像这次我们前期代码交换就是自己打包压缩包然后微信发送给对方;这样实在太乱,而且效率底下很难回溯之前的版本;基于此我们下定决心一定要学会git,花的时间虽然多但也值得!有关总结git命令可以参照下面博客:https://www.cnblogs.com/Thinker-Bob/articles/10539214.html

  • 关于详细的git学习,以及企业开发模式等等许多相关知识都有待我们抓紧学习,此次第一次入门也算是一知半解,下次争取做到更好!附上我们二人的阉割版分支管理:

  •  

  • 有关分支管理学习可参照此博客:https://www.cnblogs.com/spec-dog/p/11043371.html

8.项目完结个人PSP

 

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划 10 10
· Estimate · 估计这个任务需要多少时间 10 10
Development 开发 1015 1380
· Analysis · 需求分析 (包括学习新技术) 120 480
· Design Spec · 生成设计文档 45 0
· Design Review · 设计复审 (和同事审核设计文档) 10 0
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 120 120
· Design · 具体设计 300 120
· Coding · 具体编码 300 300
· Code Review · 代码复审 120 240
· Test · 测试(自我测试,修改代码,提交修改) 60 120
Reporting 报告 90 310
· Test Report · 测试报告 60 120
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 180
合计   1125 1700

9.感想与总结

 本次和队友一起做结对项目,收获许多。在本次结对项目,我觉得在中间阶段还是缺少了一点沟通交流,在开始阶段、临总结阶段我们都认真进行了大大小小的会议,在中间阶段遇到算法问题,两个人一时很难有解决的办法,特别是队友做的那个表达式筛选,不过从最后的结果来是不错的。我们在基本地打完代码后,一起在Intellij IDEA集成开发工具中操作过git(只进行了一半),之后是直接用git bash操作,我感觉这个项目耗时在几个主要类,还有就是git的学习,我们两个人后半程几乎都是在搞github,包括git分支的关系,git的命令结合github项目开发流程,有code review,releases,pull request,branch merge等,由于之前没细致地用过github,导致在这方面消耗了许多时间,不过也是收获良多。在感想这里点名表扬队友👍,在github上的操作都是他带我操作,大家可以关注一波❤Uncle-Drew❤。

posted on 2020-03-31 19:08  菜弟弟  阅读(242)  评论(0编辑  收藏  举报