1、GitHub地址:https://github.com/caiyouling/Myapp
队友:钟小敏 GitHub地址:https://github.com/zhongxiao136/Myapp
2.PSP表格
PSP | Personal Software Process Stages | 预计耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 30 |
.Estimate | .估计这个任务需要多少时间 | 40 | 30 |
Development | 开发 | 1320 | 1330 |
.Analysis | .需求分析 | 100 | 120 |
.Design Spec | .生成设计文档 | 40 | 30 |
.Design Review | .设计复审 | 15 | 20 |
.Coding Standard | .代码规范 | 15 | 20 |
.Design | .具体设计 | 90 | 100 |
.Coding | .具体编码 | 850 | 950 |
.Code Review | .代码复审 | 90 | 90 |
.Test | .测试(自我测试,修改代码,提交修改) | 120 | 130 |
Reporting | 报告 | 180 | 160 |
.Test Report | .测试报告 | 90 | 80 |
.Size Measurement | .计算工作量 | 30 | 40 |
.Postmortem&Process Improvement Plan | .事后总结,并提出过程改进计划 | 60 | 40 |
合计 | 1540 | 1520 |
效能分析
设计实现过程
- 解题思路:由于题目为自动生成小学四则运算题目,所以需要系统随机生成运算式。运算式包含操作数和运算符,所以我们就用了一个CreateNumAndOpe类随机生成,然后利用createExpression类组合成运算式和答案(利用Calculate类进行计算,因为结果要为真分数或整数,所以我们利用Fraction类对分数进行操作化为最终形式),然后用createFile类生成题目,答案和做题文件,最后,利用Grade类进行验证答案,并生成成绩文件。
- createNumAndOpe类:定义运算符数组;包含createNumber()和createOperator(),其中createNumber()用于生成操作数(利用Random类随机生成整数或分数);createOperator()用于生成运算符,随机从运算符数组中选出运算符。
- createExpression类:用于生成随机带括号的运算式,其中exerciseAndAnswer()根据操作数数组和运算符数组生成运算式,其中需要用Random类判断是否随机生成括号,最后用Calculate()计算出结果。getExerciseAndAnswer()根据用户输入的题目个数和数字范围生成相应的题目和答案。
- Calculate类:根据运算式转为字符数组,然后逐个遍历,建立一个运算符栈和操作数栈,利用中缀表达式转为后缀表达式进行计算。
- 因为括号的优先级最高,所以我们先判断是否为'(',若是则入运算符栈;
- 判断是否为')',是则从操作数取出两个进行运算,将'('之后的运算符都拿出来算,结果再存入操作数栈中,然后左括号出栈;
- 判断是否为运算符,当运算符优先级小于运算符栈顶元素,则取两个操作数进行运算,直至栈顶优先级不高于该运算符,结果存入操作数栈中,然后该运算符入栈;
- 若为操作数,因为生成的操作数有分数,但是我们转为了字符数组,就需要根据'/'作为标志获取分子和分母,若为整数,我们将分母设为1,把操作数都转为分数进行传到calculate()进行运算,返回结果
- Fraction类:对分数进行操作,其中getFinalFraction()用于生成最终分数形式,gcd()利用辗转相除法生成最大公约数,getRealFraction()将假分数转为真分数。
- createFile类:利用System.getProperTy()获取当前路径生成printFile文件夹,利用BufferedWriter写入题目文件,答案文件和答题文件(用户进行答题的文件),存放到printFile文件夹下
- Grade类:用于验证用户答题是否正确,利用BufferedReader读取Writer文件和answer文件进行比较,算出对错的题数及题号写入Grade文件,也存在printFile文件夹下。
- 各类之间的调用如下:
- Frame类:图形化界面,用户点击‘生成文件’,即会生成练习文件,在左面板也会出现题目,中间面板进行答题,点击查看答案,则会有正确答案在右侧面板,查看成绩后会弹出成绩框。
关键代码说明
- 用于生成操作数和运算符的方法:1.利用随机生成的flag=[0,1],当flag为0时,生成整数 2.当flag为1时,生成分子和分母组成分数
public static String[] createNumber(int number, int round){ Random random = new Random(); String[] num = new String[number]; for (int i = 0; i < number; i++) { //用于判断生成整数还是分数 int flag = (int)(Math.random()*10) % 2; if(flag == 0){ //生成整数 int n = random.nextInt(round); if(n == 0){ num[i] = 1 + ""; }else{ num[i] = n +""; } }else{ //生成分数 //随机生成分子和分母 int numerator = random.nextInt(round); int denominator = random.nextInt(round);; while(numerator>=denominator || numerator==0 || denominator==0){ //判断是否为真分数,且不能生成带0的分数 numerator = random.nextInt(round); denominator = random.nextInt(round); } //拼装成分数形式 num[i] = numerator + "/" + denominator; } } return num; } /** * 随机生成运算符 * 将四则运算符放入一个静态不可变的operatorTypes[]字符数组中 * 随机产生index到数组中取操作符 */ private static final Character[] operatorTypes = {'+' , '-' , '×' , '÷'}; public static Character[] createOperators(int number) { Character[] operators = new Character[number]; for (int i = 0; i < number; i++) { //随机获取运算符的类型(0~3 代表4个运算符(+、-、×、÷)的类型) int index = (int)(Math.random()*4); Character operatorType = operatorTypes[index]; operators[i] = operatorType; } return operators; } public static int priority(Character character) { switch(character) { case '×': case '÷':return 1; case '+': case '-':return 0; } return -1; } private static String[] exerciseAndAnswer(String[] numbers, Character[] operators){ Random random = new Random(); //获得操作数的数量 int num = numbers.length; //随机生成带括号的算式 int isAddBracket = (int)(Math.random()*10) %2; if(isAddBracket == 1){ //当isAddBracket==1时,生成带括号的表达式 //当标记为1时代表该操作数已经添加了左括号 int[] leftBracket = new int[num]; //当标记为1时代表该操作数已经添加了右括号 int[] rightBracket = new int[num]; //遍历操作数数组,随机添加括号 for (int index = 0 ; index<num-1 ; index++) { int n = (int)(Math.random()*10)%2; if(n == 0 && rightBracket[index] != 1) {//判断当前操作数是否标记了左括号 leftBracket[index] = 1; //标记已生成左括号 numbers[index] = "(" + numbers[index]; //操作数之前加上左括号 int k = num - 1; //生成右括号的位置(左括号的位置~最后) int rightBracketIndex = random.nextInt(k)%(k-index) + (index+1); //如果当前操作数有左括号,则重新生成括号位置 while (leftBracket[rightBracketIndex] == 1){ rightBracketIndex = random.nextInt(k)%(k-index) + (index+1); } rightBracket[rightBracketIndex] = 1; //标记已生成右括号 numbers[rightBracketIndex] = numbers[rightBracketIndex] +")"; } } } //将运算符数组和操作数数组交替拼成一个算式字符串 StringBuilder str = new StringBuilder(numbers[0]); for (int k = 0; k < operators.length; k++) { //组成算式 str.append(operators[k]).append(numbers[k + 1]); } //将算式转换为String String exercise = str.toString(); //获取运算式结果 String answer = Calculate.getAnswer(exercise); if(answer.equals("-")){ //运算过程出现负数则返回null return null; } return new String[]{exercise,answer}; }
public static String getAnswer(String expression) { Stack<Character> operators = new Stack<Character>(); //运算符栈,用于存放运算符 Stack<Fraction> fractions = new Stack<Fraction>(); //操作数栈,用于存放操作数 if(expression == null){ return "null"; } char[] chars = expression.toCharArray(); //将表达式字符串转成字符数组 //遍历获取处理 for (int i = 0 ; i<chars.length ; i++) { char c = chars[i]; if(c == '('){ //先处理有括号的情况,如果是左括号,入栈 operators.push(c); } else if(c == ')'){ //当前字符为右括号 while(operators.peek() != '('){ //当运算符栈顶的元素不为‘(’,则继续 Fraction fraction1 = fractions.pop(); //拿取操作栈中的两个分数 Fraction fraction2 = fractions.pop(); //获取计算后的值 Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(), fraction2.getNumerator(), fraction2.getDenominator()); if(result.getNumerator()<0){ //判断是否为负数 return "-"; } //将结果压入栈中 fractions.push(result); } //将左括号出栈 operators.pop(); }else if(isOperator(c)){//是运算符 //当运算符栈不为空,且当前运算符优先级小于栈顶运算符优先级 while(!operators.empty() && !(priority(c)>= priority(operators.peek()))){ //拿取操作栈中的两个分数 Fraction fraction1 = fractions.pop(); Fraction fraction2 = fractions.pop(); //获取计算后的值 Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(), fraction2.getNumerator(), fraction2.getDenominator()); if(result.getNumerator()<0){ return "-"; } //将结果压入栈中 fractions.push(result); } //将运算符入栈 operators.push(c); }else{ //是操作数 if(c >= '0' && c <= '9'){ StringBuilder fra = new StringBuilder(); //对分式进行处理 while(i<chars.length && (chars[i]=='/' || ((chars[i]>='0') && chars[i]<='9'))){ fra.append(chars[i]); i++; } i--; //到此 fra里面是一个操作数 String val = fra.toString(); //标记‘/’的位置 int flag = val.length(); for(int j = 0 ; j<val.length() ; j++){ if(val.charAt(j)=='/'){//当获取的数值存在/则标记/的位置,便于接下来划分分子和分母生成分数对象 flag = j; } } //把操作数拆成分式计算 //分子 StringBuilder numeratorBuf = new StringBuilder(); //分母 StringBuilder denominatorBuf = new StringBuilder(); for(int k = 0 ; k<flag; k++){ numeratorBuf.append(val.charAt(k)); } //判断是否为分数 if(flag != val.length()){ for(int m = flag+1 ; m<val.length() ; m++){ denominatorBuf.append(val.charAt(m)); } }else{//如果不是分数则分母计为1 denominatorBuf.append('1'); } //将分子分母分别入栈 fractions.push(new Fraction(Integer.parseInt(numeratorBuf.toString()), Integer.parseInt(denominatorBuf.toString()))); } } } while(!operators.empty() && fractions.size()>1){ Fraction fraction1 = fractions.pop(); Fraction fraction2 = fractions.pop(); //获取计算后的值 Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(), fraction2.getNumerator(), fraction2.getDenominator()); if(result.getNumerator()<0){ return "-"; } //将结果压入栈中 fractions.push(result); } //计算结果 Fraction result = fractions.pop(); //获取最终的结果(将分数进行约分) return Fraction.getFinalFraction(result); } //用于验证答案是否正确,correct的题数用字符串拼接,再分割字符串 public static void isAnswer(String writeFilePath, String answerFilePath) { //获取用户答题和练习答案的文件 File writeFile = new File(writeFilePath); File answerFile = new File(answerFilePath); //找到存放Writer.txt和Answer.txt文件的文件夹 String fileDirectory = System.getProperty("user.dir") + File.separator + "PrintFile"; File gradeFile = new File(fileDirectory, "Grade.txt");//在当前文件夹下生成Grade.txt文件 if(writeFile.isFile() && answerFile.isFile()) { try { BufferedReader writeReader = new BufferedReader(new InputStreamReader(new FileInputStream(writeFile))); BufferedReader answerReader = new BufferedReader(new InputStreamReader(new FileInputStream(answerFile))); //储存错题和对题 String correct = ""; String wrong = ""; int correctNum = 0; int wrongNum = 0; //记录错题对题的题号 int index = 1; String write = null; String answer = null; System.out.println("开始验证答案···"); //通过一行行读取文件比较答题情况 while((( write= writeReader.readLine()) != null) && ((answer = answerReader.readLine()) != null)){ if(write.equals(answer)){ //将答对的题用字符串拼接 correct += "," + index; index ++; correctNum ++; }else{ wrong += "," + index; index ++; wrongNum ++; } } //用于生成Grade.txt文件内容 if(correctNum > 0){ correct = "Correct: " + correctNum + "(" + correct.substring(1) +")" + "\r\n" ; }else{ correct = "Correct: 0" +"\r\n"; } if(wrongNum > 0){ wrong = "Wrong: " + wrongNum + "(" + wrong.substring(1) +")"; }else{ wrong = "Wrong: 0"; } //写入文件 BufferedWriter gradeFileBuf = new BufferedWriter(new FileWriter(gradeFile)); gradeFileBuf.write(correct);//将correct 和wrong写入文件 gradeFileBuf.write(wrong); System.out.print(correct);//控制台也打印答题情况 System.out.println(wrong); if(writeReader != null){ writeReader.close(); } if(answerReader != null){ answerReader.close(); } if(gradeFileBuf != null){ gradeFileBuf.close(); } public static String getFinalFraction(Fraction fraction) { int denominator = fraction.getDenominator(); //获得分子分母 int numerator = fraction.getNumerator(); if(numerator == 0){ //若分子为0,则输出0 return "0"; } if(denominator == 0){ return ""; } int a = gcd(numerator, denominator); //c是分子分母的最大公因数,将分子分母化简 if (denominator == 1 ) { //分母为1 return numerator + ""; } if(numerator == denominator){ return 1+""; }else { if(numerator > denominator){ //分子大于分母化为真分数 fraction = getRealFraction(fraction); //假分数分割为商和余数,余数作为新的分子 if(fraction.getNumerator() == 0){//若余数为0,则代表整除 return fraction.getInter()+""; }else { return fraction.getInter() + "'" + fraction.getNumerator()/a + "/" + fraction.getDenominator()/a; } }else{ //其他情况化简分数 return numerator/a + "/" + denominator/a; } } } private static int gcd(int numerator, int denominator){ if(numerator%denominator == 0){ return denominator; //获取最大公因数 }else{ return gcd(denominator,numerator%denominator); //辗转相除法递归获取最大公因数 } }
测试
1.启动main,运行控制台打印
2.用户输入后生成的文件列表
.
3.生成题目文件的题目
4.生成答案放入文件
5.在答题文件进行答题后
6.重新判别答案
7.传入参数不正确的情况
8.输入不正确的文件名
9.生成一万道题目
10.生成一万道题目的答案文件
11.初始界面
12.点击生成文件
13.输入生成个数
14.生成题目
15.进行作答
16.输出成绩
17.生成十道题
18.生成一万道
小结
- 本次结对项目中,我们都受益匪浅,结对项目需要多次讨论得出一致结论,两个人一起分析问题解决bug是比一个人好一些,虽然也有发生歧义的地方,但是不同角度看问题,才能实现1+1>2,这次编程作业让我们对java有了更深了了解,但也还有很多不足的