项目语言: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.1 | Personal 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题目介绍:
-
使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。
-
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 Myapp.exe -r 10 ,将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
-
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
-
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
-
每道题目中出现的运算符个数不超过3个。
-
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,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,它们之间不能通过有限次交换变成同一个题目。
-
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
四则运算题目1
四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
1.答案1
2.答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
-
程序应能支持一万道题目的生成。
-
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
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 }
-
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 }
-
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 }
5.3待开发功能…
-
有关算术表达式生成范围,目前仍未实现生成分数或者真分数
-
用户答案输入不能完全识别是否为分数,例如:3‘‘’‘’‘’‘’、/1、////、‘’‘’‘’程序也识别为答案
-
有关筛选类和生成答案类相当一部分代码功能相同但是由于分工合作交流不当编写时产生冗余部分仍需改进
-
关于判断算术表达式是否有重复功能,例如(1+2)*(3+4) 和(4+3)*(2+1)此类型表达式我们尚未想出办法解决,基于初步想法也只想出精确筛选表达式是否会有重复可以分三种情况进行,且筛选正确率逐步提升:
-
依据两式子答案相同、字符串长度相同、运算符计算优先级相同判断为两表达式等价:
-
将获取式子的优先级和运算数提取成查重表达式,例如: 原式为: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 // }
-
而对于可执行交换律的运算符“*”和“+”,当出现三个运算符都是此两种时,仍未想出具体算法可以准确且高效判断是否等价
-
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 ” 表示退出,虽然退出了,但是还是要记得校对答案哦。
-
校对答案例子(包括用户的一些错误输入处理)
-
不校对答案例子
-
支持生成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.1 | Personal 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❤。