结对项目:四则运算表达式生成程序
一、Github项目地址
https://github.com/oMIZUCHIo/MyAppProject
结对项目成员:周伟健 3118005079;周锦发 3118005078
二、PSP表格
|
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
|
Planning |
计划 |
60 |
|
|
· Estimate |
· 估计这个任务需要多少时间 |
60 |
|
|
Development |
开发 |
1600 |
|
|
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
|
|
· Design Spec |
· 生成设计文档 |
60 |
|
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
80 |
|
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
|
|
· Design |
· 具体设计 |
70 |
|
|
· Coding |
· 具体编码 |
1000 |
|
|
· Code Review |
· 代码复审 |
120 |
|
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
|
|
Reporting |
报告 |
100 |
|
|
· Test Report |
· 测试报告 |
30 |
|
|
· Size Measurement |
· 计算工作量 |
15 |
|
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
55 |
|
|
合计 |
|
1760 |
|
三、效能分析

四、设计实现过程
- MyUtil类
process()方法:对外的调用接口,根据封装的参数对象进行操作;
createProblem()方法:创建单条的表达式;
countProblem)方法:对输入的表达式进行计算,;
createOperatorNum()方法:根据输入的范围随机生成操作数字,用于被createProblem()函数调用;
count()方法:根据输入的两数及操作符进行只含一个操作符表达式的计算;
add()方法:加法,用于被count()函数调用;
sub()方法:减分,用于被count()函数调用;
muti()方法:乘法,用于被count()函数调用;
div()方法:除法,用于被count()函数调用;
converNum()方法:简单转化带分数等;
simplyResult()方法:化简计算结果为真分数;
- FileUtil类
paramJudge()方法:对输入参数进行简单的判断合法性函数;
write()方法:输出表达式文件和答案文件函数;
checkAnswers)方法:比对答案函数;
- Frame类
start()方法:创建可视化窗口进行后续操作;
五、代码说明
创建表达式相关函数:只是简单生成各操作数字及操作符并进行简单的拼接
/** * @description 创建表达式 */ private String createProblem(Parameter parameter){ Random random = new Random(); char[] operatorCharList = new char[]{'+', '-', '*', '÷' , '('}; int operatorCharScope = 5; //可使用操作符范围 String operatorNum; //操作数 boolean useLeftBrackets = false; //是否使用左括号 int numInBrackets = 0; //括号包围数字数目 //生成的式子 StringBuilder line = new StringBuilder(); //操作符数目设置为 1-3 个 int operatorCharNum = 1 + random.nextInt(2); for(int i = 0 ; i < operatorCharNum ; i ++){ //生成操作数 operatorNum = createOperatorNum(parameter.getScopeLength()); //生成操作符 char operatorChar = operatorCharList[random.nextInt(operatorCharScope)]; if(operatorChar == '('){ //如果还未使用操作符,则 加入左括号 并 生成新的不为括号的操作符 if(! useLeftBrackets){ operatorChar = operatorCharList[random.nextInt(operatorCharScope - 1)]; useLeftBrackets = true; //已使用过左括号 numInBrackets ++; line.append('(').append(' ').append(operatorNum).append(' ').append(operatorChar).append(' '); //当已使用左括号 且 括号中数字数目为0时 需更换此时操作符为 新的不为括号的操作符 }else if(numInBrackets == 0){ operatorChar = operatorCharList[random.nextInt(operatorCharScope - 1)]; line.append(operatorNum).append(' ').append(operatorChar).append(' '); //当已使用左括号 且 括号中数字数目大于1时 }else { operatorChar = operatorCharList[random.nextInt(operatorCharScope - 1)]; line.append(operatorNum).append(' ').append(')').append(' ').append(operatorChar).append(' '); operatorCharScope = 4; //可使用操作符范围设为4,之后不会再生成 括号字符 } } //操作符不是括号就直接添加对应的式子就行 line.append(operatorNum).append(' ').append(operatorChar).append(' '); } operatorNum = createOperatorNum(parameter.getScopeLength()); line.append(operatorNum); //如果使用了左括号 且 可使用操作符范围为 0-4 则说明未使用过 右括号 对左括号进行匹配,需要手动添加 if(useLeftBrackets && operatorCharScope == 5){ line.append(' ').append(')'); } String lineStr = line.toString(); if(lineStr.startsWith("(") && lineStr.endsWith(")")){ lineStr = lineStr.substring(1 , lineStr.length() - 1); } return lineStr; } /** * @description 生成操作数字 * @param range 取值范围 */ String createOperatorNum(int range){ Random random = new Random(); StringBuilder sb = new StringBuilder(); //生成整数或分数的随机标识,0表示生成整型,1表示生成分数 int flag = random.nextInt(2); if(flag == 0){ sb.append(random.nextInt(range + 1)); }else { int denominator = 1 + random.nextInt(range); //分母 int numerator = 1 + random.nextInt(range); //分子 //当分子大于分母时 if(numerator > denominator){ int intNum = numerator / denominator; //假分数前的整数 numerator = numerator % denominator; //得出新的分子 //整除 if(numerator == 0){ sb.append(intNum); }else{ sb.append(intNum).append('\'').append(numerator).append("/").append(denominator); } }else if(numerator == denominator){ sb.append(1); }else if(denominator == 1){ sb.append(numerator); }else{ sb.append(numerator).append("/").append(denominator); } } return sb.toString(); }
计算表达式相关函数:因为我们两人都不知道这方面有什么好的算法,所以表达式的计算是按自己做题的方式来写的,所以性能可能会差点
/** * @description 计算结果 * @param problem 问题 * @return java.lang.String */ String countProblem(String problem){ List<String> charList = converProblemToList(problem); //含有括号,计算括号内容结果,再用结果替换括号 if(charList.indexOf("(") >= 0){ int i = charList.indexOf("("); int temp = i; StringBuilder sb = new StringBuilder(); i ++; while (!charList.get(i).equals(")")){ sb.append(charList.get(i)).append(" "); //得出括号内的计算式 charList.remove(i); //删去括号内内容 } charList.remove(i); //删去多余的右括号 String bracketResult = countProblem(sb.toString()); //计算括号内式子 if(bracketResult.equals("error")){ return bracketResult; //出现负数 }else if(bracketResult.equals("X")){ //除数出现0 return bracketResult; } charList.set(temp,bracketResult); //用结果替换原来的左括号内容 } //此时已不含括号 , 格式: 1 + 2 * 3 + 8 //先进行乘除的优先计算 for(int i = 1 ; i < charList.size() ; ){ if(charList.get(i).equals("*") || charList.get(i).equals("÷")){ String num1 = charList.get(i - 1); String num2 = charList.get(i + 1); String countResult = count(num1,num2,charList.get(i)); if(countResult == null){ return "X"; //除数出现0 } System.out.println("num1 " + num1 + " " + charList.get(i) + " " + "num2 " + num2 + " = " + countResult); charList.set(i - 1,countResult); charList.remove(i + 1); charList.remove(i); System.out.println("size:" + charList.size() + ";" + charList.toString()); }else{ i = i + 2; //不为乘除号则查询下一个操作符 } } //集合大小不为1则说明还未计算完成,此时式中只剩加减号 if(charList.size() != 1){ for(int i = 1 ; i < charList.size() ; ){ String num1 = charList.get(i - 1); String num2 = charList.get(i + 1); String countResult = count(num1,num2,charList.get(i)); if(countResult == null){ return "error"; //出现负数 } charList.set(i - 1,countResult); charList.remove(i + 1); charList.remove(i); } } return charList.get(0); } /** * @description 两数计算 * @param num1 数1 * @param num2 数2 * @param operatorChar 操作符 * @return java.lang.String */ String count(String num1, String num2, String operatorChar){ String result; int[] result1 = converNum(num1); int[] result2 = converNum(num2); int numerator1 = result1[0]; int denominator1 = result1[1]; int numerator2 = result2[0]; int denominator2 = result2[1]; System.out.println(numerator1 + "/" + denominator1 + " " + numerator2 + "/" + denominator2); switch (operatorChar){ case "+" : result = add(numerator1,denominator1,numerator2,denominator2);break; case "-" : result = sub(numerator1,denominator1,numerator2,denominator2);break; case "*" : result = muti(numerator1,denominator1,numerator2,denominator2);break; case "÷" : result = div(numerator1,denominator1,numerator2,denominator2);break; default : result = null; } return result; }
化简相关:
/** * @description 化简计算结果 * @param result 结果 * @return java.lang.String */ String simplyResult(String result){ if(result.equals("X")){ return result; //特殊情况直接返回 } String[] splitResult = result.split("/"); //因为结果必为真分数/或整型,所以直接分割 //本身结果就是整型 if(splitResult.length == 1){ return result; //不做处理 }else { int numerator = Integer.valueOf(splitResult[0]); //分子 int denominator = Integer.valueOf(splitResult[1]); //分母 //若分母为1,直接返回分子 if(denominator == 1){ return String.valueOf(numerator); //若分子为0,直接返回0 }else if(numerator == 0){ return "0"; }else{ //分母整除分子 if(denominator % numerator == 0){ return 1 + "/" + denominator / numerator; }else{ int intNum = numerator / denominator; //假分数前的整数 numerator = numerator % denominator; //得出新的分子 //余数为0 整除 if(numerator == 0){ //返回整除结果 return String.valueOf(intNum); }else{ if(intNum == 0){ return simplyDivision(numerator,denominator); }else{ if(denominator % numerator == 0){ return intNum + "'" + "1/" + denominator / numerator; }else{ return intNum + "'" + simplyDivision(numerator,denominator); } } } } } } } /** * @description 化简真分数 * @param numerator 分子 * @param denominator 分母 * @return java.lang.String */ private String simplyDivision(int numerator, int denominator){ int y = 1; for (int i = numerator ; i >= 1; i--) { if (numerator % i == 0 && denominator % i == 0) { y = i; break; } } int z = numerator / y;// 分子 int m = denominator / y;// 分母 if (z == 0) { return "0"; } if(m == 1){ return String.valueOf(z); } return z + "/" + m; }
文件类相关:采用IO流进行操作
/** * @param problemStr 问题 * @param resultStr 答案 * @param directoryPath 当前文件夹路径 * @return java.lang.String * @description 将信息写入文件中 */ String wirte(String problemStr, String resultStr, String directoryPath) { try { File problemFile = new File(directoryPath + "\\Exercises.txt"); boolean flag1 = problemFile.createNewFile(); File resultFile = new File(directoryPath + "\\Answers.txt"); boolean flag2 = resultFile.createNewFile(); if (!(flag1 && flag2)) { return "创建文件出错"; } FileOutputStream fos = new FileOutputStream(problemFile.getAbsolutePath()); fos.write(problemStr.getBytes()); fos.flush(); fos = new FileOutputStream(resultFile.getAbsolutePath()); fos.write(resultStr.getBytes()); fos.close(); return null; } catch (Exception e) { return "文件写入出错,错误原因:" + e.getMessage(); } } /** * @description 比对答案 * @param trueAnswersPath 用户答案路径 * @param myAnswersPath 正确答案路径 * @param directoryPath 成绩输出文件夹 * @return java.lang.String */ String checkAnswers(String trueAnswersPath, String myAnswersPath, String directoryPath) { try { BufferedReader myreader = new BufferedReader(new FileReader(myAnswersPath)); BufferedReader truereader = new BufferedReader(new FileReader(trueAnswersPath)); StringBuilder trueResult = new StringBuilder(); StringBuilder falseResult = new StringBuilder(); int trueNum = 0; int falseNum = 0; String[] myStrings; String[] trueStrings; String myLine = null; //用户答案逐行 String trueLine = null; //正确答案逐行 String myAnswers = null; //用户答案 String trueAnswers = null; //正确答案 int myIndex = 0; int trueIndex = 0; while ((myLine = myreader.readLine()) != null) { if (myLine.trim().equals("")) { //跳过空行 continue; } trueLine = truereader.readLine(); while(trueLine.trim().equals("")){ //跳过空行 trueLine = truereader.readLine(); } myStrings = myLine.split(". "); trueStrings = trueLine.split(". "); myIndex = Integer.valueOf(myStrings[0].trim()); //题目序号 trueIndex = Integer.valueOf(trueStrings[0].trim()); while(myIndex > trueIndex){ //使答案序号一致 trueLine = truereader.readLine(); while(trueLine.trim().equals("")){ //跳过空行 trueLine = truereader.readLine(); } trueStrings = trueLine.split(". "); trueIndex = Integer.valueOf(trueStrings[0].trim()); } while(myIndex < trueIndex){ //使答案序号一致 myLine = myreader.readLine(); while(myLine.trim().equals("")){ //跳过空行 myLine = myreader.readLine(); } myStrings = myLine.split(". "); myIndex = Integer.valueOf(myStrings[0].trim()); } if (myStrings.length == 1) { //无答案直接算错 falseResult.append(myIndex).append(","); falseNum ++; continue; } myAnswers = myLine.split(". ")[1].trim(); //用户答案 trueAnswers = trueLine.split(". ")[1].trim(); //正确答案 if(myAnswers.equals(trueAnswers)){ trueResult.append(myIndex).append(","); trueNum ++; }else{ falseResult.append(myIndex).append(","); falseNum ++; } } myreader.close(); String trueStr = trueResult.toString(); String falseStr = falseResult.toString(); File outPutFile = new File(directoryPath + "\\Grade.txt"); int i = 1; while(outPutFile.exists()){ //防止文件名重复造成的文件创建失败 outPutFile = new File(directoryPath + "\\Grade(" + i + ").txt"); i ++; } boolean flag = outPutFile.createNewFile(); if (!flag) { return "创建成绩输出文件出错"; } FileOutputStream fos = new FileOutputStream(outPutFile.getAbsolutePath()); String grade = "Correct: " + trueNum + "(" + trueStr.substring(0,trueStr.length() - 1) + ")\n\n" + "Wrong: " + falseNum + "(" + falseStr.substring(0,falseStr.length() - 1) + ")"; fos.write(grade.getBytes()); fos.close(); } catch (Exception e) { return "答案格式出错!,每道题格式如:1. 1/2(序号.空格+答案)," + e.getMessage(); } return null; }
六、测试运行
生成10000道题目


修改9995答案及10000题答案,结果:

七、PSP表格(完成)
|
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
|
Planning |
计划 |
60 |
50 |
|
· Estimate |
· 估计这个任务需要多少时间 |
60 |
50 |
|
Development |
开发 |
1600 |
1900 |
|
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
200 |
|
· Design Spec |
· 生成设计文档 |
60 |
80 |
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
80 |
150 |
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
40 |
|
· Design |
· 具体设计 |
70 |
60 |
|
· Coding |
· 具体编码 |
1000 |
1300 |
|
· Code Review |
· 代码复审 |
120 |
100 |
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
150 |
|
Reporting |
报告 |
100 |
150 |
|
· Test Report |
· 测试报告 |
30 |
50 |
|
· Size Measurement |
· 计算工作量 |
15 |
20 |
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
55 |
80 |
|
合计 |
|
1760 |
2100 |
八、项目小结
本次的结对项目作业,对于我们两个人而言都是一个挺好的锻炼机会,我们在项目开始搭建前便一起进行了基础的分工,以及各板块的基本设计方式,这使得我们后面项目的进行相对比较顺利。在这个过程中,我们相互学习各自的优点,一起讨论解决项目中出现的BUG,进行了一次难得的团队开发经历,对我们各自的技能提升都有着一定的帮助。但不可忽视的是,我们在项目进行期间也出现了一些问题,这次项目因为赶时间也没有完成得特别好,像去重的任务我们到现在也没有想到一个好的解决方式...
周伟健的闪光点:有比较好的代码执行能力,做事有比较不错的想法
周锦发的闪光点:任劳任怨,善于倾听队友想法,有比较好的解决问题的能力
浙公网安备 33010602011771号