github地址:https://github.com/liyizhu/myapp
项目相关要求
(1)【实现】使用 -n 参数控制生成题目的个数。
(2)【实现】使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围。
(3)【实现】生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
(4)【实现】生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
(5)【实现】每道题目中出现的运算符个数不超过3个。
(6)【实现】程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件
(7)【实现】在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。
(8)【实现】程序应能支持一万道题目的生成。
(9)【实现】程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。
PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
35 |
· Estimate |
· 估计这个任务需要多少时间 |
15 |
20 |
Development |
开发 |
700 |
720 |
· Analysis |
· 需求分析 (包括学习新技术) |
200 |
170 |
· Design Spec |
· 生成设计文档 |
50 |
60 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
25 |
30 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
5 |
10 |
· Design |
· 具体设计 |
15 |
25 |
· Coding |
· 具体编码 |
300 |
350 |
· Code Review |
· 代码复审 |
50 |
45 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
60 |
100 |
Reporting |
报告 |
120 |
150 |
· Test Report |
· 测试报告 |
50 |
70 |
· Size Measurement |
· 计算工作量 |
10 |
15 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
40 |
合计 |
|
1660 |
1840 |
解题思路描述
(1)使用 -n 参数控制生成题目的个数。
(2)使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
思路:将参数传进实参传递给相关函数
(3)生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
思路:先生成题目,然后使用堆栈判断是否计算过程产生负数,产生负数的式子直接丢弃,重新生成
(4)生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
思路:先生成题目,然后使用堆栈判断是否计算过程产生负数,产生负数的式子直接丢弃,重新生成
(5)每道题目中出现的运算符个数不超过3个。
思路:产生题目的过程是每次往StringBuilder后面添加一个运算符加一个操作数,当添加数字达到三之后就不再添加,保证操作符个数不超过三
(6)程序一次运行生成的题目不能重复,任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件
思路:题目判重通过将题目按照优先级计算的过程存入堆栈,然后比较两个堆栈的内容是否一致
(7)在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。
思路:生成题目时调用计算答案的方法,使用字符输出流将答案写入文件
(8)程序应能支持一万道题目的生成。
思路:循环生成指定数目的题目
(9)程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。
思路:读出题目文件中的答案和答案文件中的答案进行比较
设计实现过程
流程图:
代码说明
ProperFraction类:用于随机生成一个随机数。
package com.liyizhu.myapp; import java.util.Random; public class ProperFraction { public int numerator;//分子 public int denominator;//分母 private Random random = new Random(); /* * 随机获取一个数 */ public ProperFraction getRandom(int Max) { if(random.nextBoolean()) { return getInt(Max); }else { return getProperFraction(Max); } } /* * 获取整数 */ public ProperFraction getInt(int Max) { numerator = MyappUtil.getInt(Max); denominator = 1; return this; } /* * 获取分数 */ public ProperFraction getProperFraction(int Max) { numerator = MyappUtil.getInt(Max-1)+1; denominator = MyappUtil.getInt(Max-1)+1; int i = numerator>denominator?denominator:numerator; for(;i>1;i--) { if(numerator%i==0&&denominator%i==0) { numerator /= i; denominator /= i; break; } } return this; } @Override public String toString() { String string = null; if(numerator==0) { return "0"; } if(numerator
MyappUtil类:工具类,包括分数约分、分数加减乘除计算、运算符的计算优先级等方法。
package com.liyizhu.myapp; import java.util.Random; public class MyappUtil { private static Random random = new Random(); /* * 随机获取小于Max随机整数 */ public static int getInt(int Max) { return random.nextInt(Max); } /* * 随机获取操作符 */ private static char[] operators = {'+','-','*','/'}; public static char getOperator() { return operators[random.nextInt(4)]; } /* * 计算优先级 */ private static int calculatePriority(String operator) { switch (operator) { case "+": case "-": return 0; case "*": case "/": return 1; default: return -1; } } /* * 比较优先级 */ public static int newComparePriority(String a,String b) { if(calculatePriority(a)>calculatePriority(b)) { return 1; }else if(calculatePriority(a)=calculatePriority(b)) { return true; }else { return false; } } /* * 将字符串转换为分数 */ private static ProperFraction translate(String s) { ProperFraction properFraction = new ProperFraction(); if(s.contains("'")) { String[] strings = s.split("['/]"); properFraction.numerator = Integer.parseInt(strings[0])*Integer.parseInt(strings[2]) + Integer.parseInt(strings[1]); properFraction.denominator = Integer.parseInt(strings[2]); }else if(s.contains("/")){ String[] strings = s.split("/"); properFraction.numerator = Integer.parseInt(strings[0]); properFraction.denominator = Integer.parseInt(strings[1]); }else { properFraction.numerator = Integer.parseInt(s); properFraction.denominator = 1; } return properFraction; } /* * 将分数约分 */ public static ProperFraction simplify(ProperFraction p) { int i = p.numerator>p.denominator?p.denominator:p.numerator; for(;i>1;i--) { if(p.numerator%i==0&&p.denominator%i==0) { p.numerator /= i; p.denominator /= i; break; } } return p; } /* * 实现加法 */ public static ProperFraction add(String s1,String s2) { ProperFraction p1 = translate(s1); ProperFraction p2 = translate(s2); ProperFraction p = new ProperFraction(); p.numerator = p1.numerator*p2.denominator + p1.denominator*p2.numerator; p.denominator = p1.denominator*p2.denominator; return simplify(p); } /* * 实现减法 */ public static ProperFraction sub(String s1,String s2) { ProperFraction p1 = translate(s1); ProperFraction p2 = translate(s2); ProperFraction p = new ProperFraction(); p.numerator = p1.numerator*p2.denominator - p1.denominator*p2.numerator; p.denominator = p1.denominator*p2.denominator; return simplify(p); } /* * 实现乘法 */ public static ProperFraction multiply(String s1,String s2) { ProperFraction p1 = translate(s1); ProperFraction p2 = translate(s2); ProperFraction p = new ProperFraction(); p.numerator = p1.numerator*p2.numerator; p.denominator = p1.denominator*p2.denominator; return simplify(p); } /* * 实现除法 */ public static ProperFraction div(String s1,String s2) { ProperFraction p1 = translate(s1); ProperFraction p2 = translate(s2); ProperFraction p = new ProperFraction(); p.numerator = p1.numerator*p2.denominator; p.denominator = p1.denominator*p2.numerator; return simplify(p); } }
Exercise类:生成一个表达式
package com.liyizhu.myapp; import java.util.Random; import java.util.Stack; public class Exercise { public String expression[]; public Stackstack = new Stack (); public void getCheckStack(String s){ Stack operatedNumber = new Stack (); Stack operator = new Stack (); String currentOperator = null; boolean isBracket = false;//是否进入括号内 s = s.substring(0,s.length()-2); String[] strings = s.split(" "); for(int i=0;i =0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else if("-".equals(temp)){ operatedNumber.push(MyappUtil.sub(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); }else if("*".equals(temp)) { operatedNumber.push(MyappUtil.multiply(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else { operatedNumber.push(MyappUtil.div(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); } operator.pop(); isBracket = false; break; } case "+": case "-": if(isBracket) { operator.push(strings[i]); }else if(currentOperator==null) { currentOperator = strings[i]; operator.push(strings[i]); }else { if(MyappUtil.newComparePriority(strings[i], currentOperator)<=0) { String b = operatedNumber.pop();//后操作数 String a = operatedNumber.pop();//前操作数 String temp = operator.pop(); if("+".equals(temp)) { operatedNumber.push(MyappUtil.add(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else if("-".equals(temp)){ operatedNumber.push(MyappUtil.sub(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); }else if("*".equals(temp)) { operatedNumber.push(MyappUtil.multiply(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else { operatedNumber.push(MyappUtil.div(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); } operator.push(strings[i]); currentOperator = strings[i]; }else { operator.push(strings[i]); currentOperator = strings[i]; } } break; case "*": case "/": if(isBracket) { operator.push(strings[i]); }else if(currentOperator==null) { currentOperator = strings[i]; operator.push(strings[i]); }else { if(MyappUtil.newComparePriority(strings[i], currentOperator)<=0){ String b = operatedNumber.pop();//后操作数 String a = operatedNumber.pop();//前操作数 String temp = operator.pop(); if("+".equals(temp)) { operatedNumber.push(MyappUtil.add(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else if("-".equals(temp)){ operatedNumber.push(MyappUtil.sub(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); }else if("*".equals(temp)) { operatedNumber.push(MyappUtil.multiply(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else { operatedNumber.push(MyappUtil.div(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); } operator.push(strings[i]); currentOperator = strings[i]; }else if(!strings[i+1].equals("(")){ String b = strings[i+1];//后操作数 String a = operatedNumber.pop();//前操作数 String temp = strings[i]; if("+".equals(temp)) { operatedNumber.push(MyappUtil.add(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else if("-".equals(temp)){ operatedNumber.push(MyappUtil.sub(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); }else if("*".equals(temp)) { operatedNumber.push(MyappUtil.multiply(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); }else { operatedNumber.push(MyappUtil.div(a, b).toString()); stack.push(a); stack.push(b); stack.push(temp); } operator.push(strings[i]); currentOperator = strings[i]; }else { operator.push(strings[i]); currentOperator = strings[i]; } } break; default: operatedNumber.push(strings[i]); break; } } while(!operatedNumber.isEmpty()&&!operator.isEmpty()) { String b = operatedNumber.pop();//后操作数 String a = operatedNumber.pop();//前操作数 String temp = operator.pop(); switch (temp) { case "+": operatedNumber.push(MyappUtil.add(a, b).toString()); if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); break; case "-": stack.push(a); stack.push(b); stack.push(temp); operatedNumber.push(MyappUtil.sub(a, b).toString()); break; case "*": if(a.compareTo(b)>=0) { stack.push(a); stack.push(b); }else { stack.push(b); stack.push(a); } stack.push(temp); operatedNumber.push(MyappUtil.multiply(a, b).toString()); break; case "/": stack.push(a); stack.push(b); stack.push(temp); operatedNumber.push(MyappUtil.div(a, b).toString()); break; default: break; } } } public static String[] getExpression(int Max) { String[] strings = new String[2]; do { isIllegal = true; strings[0] = getExercise(Max); strings[1] = answerOfExercise(strings[0]); }while(!isIllegal); return strings; } /* * 获取一道四则运算题 */ public static String getExercise(int Max) { StringBuilder sb = new StringBuilder(); ProperFraction p = new ProperFraction(); Random random = new Random(); //生成不含括号的表达式 int i = 0; do{ if(i==0) { sb.append(p.getRandom(Max)+" "+MyappUtil.getOperator()+" "+p.getRandom(Max)); }else { sb.append(" "+MyappUtil.getOperator()+" "+p.getRandom(Max)); } i++; }while(random.nextBoolean()&&i<3); //添加括号 String[] strings = sb.toString().split(" "); if(strings.length==5) { if(random.nextBoolean()) { if(MyappUtil.comparePriority(strings[1],strings[3])){ sb = new StringBuilder(strings[0]+" "+strings[1]+" ( "+strings[2]+" "+strings[3]+" "+strings[4]+" )"); }else { sb = new StringBuilder("( "+strings[0]+" "+strings[1]+" "+strings[2]+" ) "+strings[3]+" "+strings[4]); } } } if(strings.length==7) { boolean bool1 = true;//标志中间位置是否生成括号 boolean bool2 = true;//标志开始位置是否生成括号 if(random.nextBoolean()) { if(MyappUtil.comparePriority(strings[1],strings[3])){ sb = new StringBuilder(strings[0]+" "+strings[1]+" ( "+strings[2]+" "+strings[3]+" "+strings[4]+" )" +" "+strings[5]+" "+strings[6]); bool1 = false; }else{ sb = new StringBuilder("( "+strings[0]+" "+strings[1]+" "+strings[2]+" ) "+strings[3]+" "+strings[4] +" "+strings[5]+" "+strings[6]); bool2 = false; } } strings = sb.toString().split(" "); if(bool1&&random.nextBoolean()) { if(MyappUtil.comparePriority(strings[3], strings[5])) { if(bool2) { sb = new StringBuilder(strings[0]+" "+strings[1]+" "+strings[2]+" "+strings[3]+" ( "+strings[4] +" "+strings[5]+" "+strings[6]+" )"); }else { sb = new StringBuilder(strings[0]+" "+strings[1]+" "+strings[2]+" "+strings[3]+" "+strings[4] +" "+strings[5]+" ( "+strings[6]+" "+strings[7]+" "+strings[8]+" )"); } }else { if(bool2) { sb = new StringBuilder(strings[0]+" "+strings[1]+" ( "+strings[2]+" "+strings[3]+" "+strings[4] +" ) "+strings[5]+" "+strings[6]); } } } } sb.append(" "+"="); return sb.toString(); } public static boolean isIllegal = true; /* * 判断表达式是否合法并获取表达式答案 */ public static String answerOfExercise(String s) { Stack operatedNumber = new Stack (); Stack operator = new Stack (); String currentOperator = null; boolean isBracket = false;//是否进入括号内 s = s.substring(0,s.length()-2); String[] strings = s.split(" "); for(int i=0;i stack1 = exercise.stack; while(!stack.isEmpty()&&!stack1.isEmpty()) { if(!stack.pop().equals(stack1.pop())) { return false; } } if(stack.isEmpty()&&stack1.isEmpty()) { return true; } return false; } }
Exercises类:用于生成指定数目的练习题,存在hashSet中
package com.liyizhu.myapp; import java.util.HashSet; import java.util.Set; public class Exercises { /* * 外部获取保存运算式子的Set */ public static SetgetExercisesSet(int numOfExercises,int Max){ Set set = new HashSet<>(); while(set.size()
MyappMain类:主函数类,用于判断输入的命令执行相应操作
package com.liyizhu.myapp; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Iterator; import java.util.Scanner; import java.util.Set; public class MyappMain { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入命令:"); String s=scanner.nextLine(); String[] orders = s.trim().split(" "); if(orders.length==5) { if("Myapp".equals(orders[0])&&"-n".equals(orders[1])&&"-r".equals(orders[3])) { int numOfExercises = Integer.parseInt(orders[2]); int Max = Integer.parseInt(orders[4]); BufferedWriter bw; BufferedWriter bw1; try { bw = new BufferedWriter(new FileWriter(new File("E:\\exercise.txt"))); bw1 = new BufferedWriter(new FileWriter(new File("E:\\answer.txt"))); Setset = Exercises.getExercisesSet(numOfExercises, Max); Iterator eIterator = set.iterator(); int i = 0; while(eIterator.hasNext()) { i++; String[] string = eIterator.next().expression; bw.write(i+", "+string[0]); bw.newLine(); bw1.write(i+", "+string[1]); bw1.newLine(); } bw.close(); bw1.close(); } catch (IOException e) { e.printStackTrace(); } }else if("Myapp".equals(orders[0])&&"-e".equals(orders[1])&&"-a".equals(orders[3])){ try { BufferedReader br1 = new BufferedReader(new FileReader(new File(orders[2]))); BufferedReader br2 = new BufferedReader(new FileReader(new File(orders[4]))); int i=0; int numOfCorrect = 0; int numOfError = 0; StringBuilder corret = new StringBuilder("("); StringBuilder error = new StringBuilder("("); String e = null; String a = null; while(true) { i++; try { if((e=br1.readLine())!=null&&(a=br2.readLine())!=null){ if(e.split("=")[1].trim().equals(a.split("\\s+")[1].trim())) { corret.append(i+","); numOfCorrect++; }else { error.append(i+","); numOfError++; } }else { break; } } catch (IOException e1) { e1.printStackTrace(); } } br1.close(); br2.close(); corret.deleteCharAt(corret.length()-1); error.deleteCharAt(error.length()-1); System.out.println("Corret: "+numOfCorrect+corret.toString()+")"); System.out.println("Error: "+numOfError+error.toString()+")"); } catch (FileNotFoundException e) { System.out.println("文件不存在"); } catch (IOException e1) { e1.printStackTrace(); } }else{ System.out.println("错误命令"); System.out.println("输入\"Myapp help\"或\"Myapp ?\"查看帮助文档"); } }else if(orders.length==2){ if("Myapp".equals(orders[0])&&("help".equals(orders[1])||"?".equals(orders[1]))){ System.out.println(); System.out.println("Myapp -n 生成题目数目 -r 生成题目数值的最大值 功能:生成题目"); System.out.println("Myapp -e .txt -a .txt 功能:判定答案中的对错并进行数量统计"); } }else { System.out.println("错误命令"); System.out.println("输入\"Myapp help\"或\"Myapp ?\"查看帮助文档"); } } }
测试运行
生成题目和答案文件:
统计答案中对错的的数目:
代码覆盖率:
效能分析
程序开始阶段,时间消耗最大的是在判重方面,因为一开始的方案是将新生成的题目与前面生成的题目一一进行比较,所以效率不高,后面改用hashSet存储生成的题目,只需要改写题目类的hashcode方法和equals方法即可实现判重,而且hashSet底层使用哈希算法进行查找,效率明显提高,hashcode方法进行第一次比较,考虑到题目重复的概率不高,所以在hashcode方法中比较题目的答案是否一致,equals方法中,将题目按计算的优先级,将题目压入栈中,通过比较题目的栈是否相同进行比较。
项目小结
相比个人编程,在编写代码时,一旦出现输入错误,就会有人及时的提醒。在设计代码时,有个同伴可以一起讨论,融合两个人不同的见解和观点,得出更好的方法。这次与我结对的是李奕柱同学。这次作业我们首先分析了需求,然后一起讨论各功能的实现,完成代码的设计文档和整体的结构设计,并对设计进行多次的探讨修改。设计过程中,他提出了一些优秀方法与观点,同时我也发现之前我编程的一些坏习惯,让我受益匪浅。