结对项目-自动生成小学四则运算题目的命令行程序
一、Github
Github地址:https://github.com/shishukon/arithmetic
项目成员:向宇,3118005070;周梓波 3118005080。
二、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
60 |
|
· Estimate |
· 估计这个任务需要多少时间 |
30 |
|
Development |
开发 |
1540 |
|
· Analysis |
· 需求分析 (包括学习新技术) |
50 |
|
· Design Spec |
· 生成设计文档 |
30 |
|
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
|
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
|
· Design |
· 具体设计 |
180 |
|
· Coding |
· 具体编码 |
1200 |
|
· Code Review |
· 代码复审 |
30 |
|
· Test |
· 测试(自我测试,修改代码,提交修改) |
40 |
|
Reporting |
报告 |
120 |
|
· Test Report |
· 测试报告 |
60 |
|
· Size Measurement |
· 计算工作量 |
30 |
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
|
合计 |
|
1750 |
|
三、效能分析
分析:
通过观察可以发现,字符串数据占用了大量的内存,在设计初期没有选择合适的算法,导致了字符串数组的滥用;
在中期稍微改进了对字符串数组的使用,但有几处代码写死了,使得部分数组不能重复利用,造成了很大的浪费。
四、设计实现过程及工作分配
1、使用的编程语言:
java。
2、解题思路:
题目中比较重要的部分即算式的生成,分式的表示以及重复算式的检测。
(1)算式的生成:因为最多需要包含三个运算符,在看了题目文档后决定通过两个子算式的组合来得到最终需要的算式,由于算式是字符串存储的,在转化为字符串后进行计算会十分麻烦,所以在进行算式组装时,会记录下该算式的得数,方便最后答案的计算;
(2)分式的表示:每一次式子生成时对他的运算结果进行处理并保留,在合成最终公式时再进行统一的运算和处理;
(3)重复算式的检测:最后是通过答案的比对和式子长度进行模糊的判断。
3、项目文件夹结构
arithmetic-master ├── .idea ├── Answers // 存放答案文件 ├── Exercises // 存放题目文件 ├── Grade // 存放对错判断结果 ├── out ├── src │ ├── FileName.java // 统计当前已有文件数量 │ ├── FileOperate.java // 输出题目、答案文件,判断对错 │ ├── Get_NewFormula.java // 获得四则运算公式 │ └── Instruction // 指令操作模块 ├── 图片 //md文档图片 ├── PSP.docx // PSP表格 └──readme.md // md文档
4、类、函数之间的联系
五、关键代码与设计说明
1、指令模块
//实现基本菜单功能,可以展示出题目文件夹中有多少题目文件(菜单部分省略) public class Instruction { while(true){ System.out.println("_______________________________"); System.out.println(" "); if(range == 0 || number_of_questions == 0){ //数量、范围两者有一未输入时 if (number_of_questions == 0 ) System.out.println("未输入题目数量"); else System.out.println("题目数量已记录,将生成题目数量为:" + number_of_questions); if (range == 0 ) System.out.println("未输入数值范围"); else System.out.println("数值范围已记录,数值的范围为:" + range);} else{ System.out.println("生成题目ing..."); new Get_NewFormula(range,number_of_questions);//题目生成 ... continue; } Scanner scanner = new Scanner(System.in); // 创建Scanner对象 System.out.print("输入你的指令:"); // 打印提示 String arr = scanner.nextLine(); // 读取一行输入并获取字符串 String [] instruction = arr.split("\\s+");//空格分割字符串 switch (instruction[0]){ case "-n": number_of_questions = Integer.parseInt(instruction[1]); break; case "-r": range = Integer.parseInt(instruction[1]); break; case "-e": //-e Exercise1.txt -a Answers1.txt new FileOperate(instruction[1], instruction[3]); //instruction[1]是题目文件名,instruction[3]是答案文件名 System.out.println("对错判断完毕,请在Grade文件夹中查看"); break; } } } }
2、检测文件夹中的题目文件
//获取文件名称并放入字符串数组 public class FileName { String[] Exercises = new String[100];//用于存放题目的名字 String[] Answers = new String[100];//用于存放题目的名字 FileName(){ //将题目文件存入数组 int i = 0; File file = new File("Exercises"); String[] list = file.list(); assert list != null; for (String string : list) { Exercises[i] = string;//将题目文件名字放入字符串数组 i ++; } //将答案文件存入数组 i = 0; File file2 = new File("Answers"); String[] list2 = file2.list(); assert list2 != null; for (String string : list2) { if(string.substring(string.length() - 3).equals("txt")){ Answers[i] = string;//将答案文件名字放入字符串数组 i ++;} } }
3、算式生成
(1)子公式生成
class Formula{ int value; //数的和,单个数的时候是本身,两个数的时候是两数的运算结果 int value1; //两个数的时候第一个数 int value2; //两个数的时候第二个数 int isFu = 0;//isFu是标志是否负数。 String formula,fenShu;//fenShu用来存放分数值,formula用来存放算式 char symbol; int swap_or_not = 0; //是否出现负数需要调换 int fraction_or_not = 0; //是否是分式 public char[] all_symbol = new char[]{'+', '-', '*', '/'}; public Formula(int range){ if((Math.random()*2) <=1){ //只有一数的情况 value = (int)(Math.random()*(range-1)+1); formula = String.valueOf(value); symbol = '\0'; } else{ value1 = (int)(Math.random()*range + 1); value2 = (int)(Math.random()*range + 1);//两个数的情况 symbol = all_symbol[(int) (Math.random()*4)]; if (symbol == '/' && value1 % value2 != 0){ fraction_or_not = 1; fenShu = fraction(value1 ,value2); }else value = get_value(value1, value2, symbol); if (swap_or_not == 1){ value = -value; formula = "(" + value2 + symbol + value1 + ")"; }else formula = "(" + value1 + symbol + value2 + ")"; } } public int get_value(int a,int b, char symbol){//当两数是整数时,使用该方法获得得数 switch (symbol) { case '+': return a + b; case '-': if(a<b) swap_or_not = 1; return a - b; case '/': return a / b; case '*': return a * b; } return 0; } public String get_answer(int a,int b,int c, int d,char symbol){//当两数中至少一个为分式时,用该方法获得得数 int fenZi,fenMu; switch (symbol) { case '+': fenZi = a * d + b * c; fenMu = b * d; return fraction(fenZi,fenMu); case '-': fenZi = a * d - b * c; fenMu = b * d; if(fenZi<0) { isFu = 1; return fraction(-fenZi,fenMu); } return fraction(fenZi,fenMu); case '/': fenZi = a * d; fenMu = b * c; return fraction(fenZi,fenMu); case '*': fenZi = a * c; fenMu = b * d; return fraction(fenZi,fenMu); } return null; } public String fraction(int a,int b){ //处理分数 int zhengShu = a / b, fenZi = a - zhengShu * b,i,maxYinShu = 1; for (i = 1;i <= fenZi;i++){//找最大公因数,化简分数 if (fenZi % i == 0 && b % i == 0) maxYinShu = i; } String fenShu; if (zhengShu == 0) fenShu = String.valueOf(fenZi / maxYinShu) + '/' +String.valueOf(b / maxYinShu); else fenShu = String.valueOf(zhengShu) + '’' + String.valueOf(fenZi / maxYinShu) + '/' +String.valueOf(b / maxYinShu); return fenShu; } }
(2)子公式组合
//通过if判断式子中包含分式或者整数,进行不同的处理 class get_Formula{ //用于组合两个算式 int value; // String answer; String formula,answer; Formula f1; Formula f2; char symbol; int swap_or_not = 0; //是否出现负数需要调换 public char[] all_symbol = new char[]{'+', '-', '*', '/'}; get_Formula(int range){ this.f1 = new Formula(range); this.f2 = new Formula(range); symbol = all_symbol[(int) (Math.random()*4)]; if (f1.fraction_or_not == 0 && f2.fraction_or_not == 0){ //都不是分式 if(f2.value==0 && symbol=='/') //除数为0 formula = null; else{ if (symbol=='/' && f1.value % f2.value != 0){ //两个式子不能整除时 answer = f1.fraction(f1.value,f2.value);//补充个标记 formula = this.f1.formula + symbol + this.f2.formula; } else{ //普通情况 value = f1.get_value(f1.value, f2.value, symbol); //得到答案,仅仅是整数 (之后分式功能写完后要分分式的运算和整数的运算) if(value < 0){ //结果为负数,答案取反,调换两个算式的位置 value = -value; answer = "" + value; formula = this.f2.formula + symbol + this.f1.formula; } else answer = "" + value; formula = this.f1.formula + symbol + this.f2.formula; } } }else if (f1.fraction_or_not == 1 && f2.fraction_or_not == 1){ //两个都是分式的情况 ...} } }
4、生成题目文件以及判断对错
public class FileOperate {//传式子跟答案进文件 FileOperate(String[] formula,String[] answer,String exercisesName,String answersName,int j){ try{ File writeName1 = new File("./Exercises/" + exercisesName); BufferedWriter out1 = new BufferedWriter(new FileWriter(writeName1)); File writeName2 = new File("./Answers/" + answersName); BufferedWriter out2 = new BufferedWriter(new FileWriter(writeName2)); for (int i = 1;i <= j;i++){ out1.write(i + "、" + formula[i-1] + "=" + "\n"); out1.flush(); out2.write(i + "、" + formula[i-1] + "=" + answer[i-1] + "\n"); out2.flush(); } out1.close(); out2.close(); } catch (IOException e) { e.printStackTrace(); } } FileOperate(String exercisesName,String answersName) throws IOException {//取式子跟答案比对 int i = 0;//用来记录第几道题 int j = 0,t = 0;//j是正确的题数,t是错误的题数 String w,q; String[] correct = new String[10000]; String[] wrong = new String[10000]; File writeName1 = new File("./Exercises/" + exercisesName); BufferedReader out1 = new BufferedReader(new FileReader(writeName1)); File writeName2 = new File("./Answers/" + answersName); BufferedReader out2 = new BufferedReader(new FileReader(writeName2)); while ((w = out1.readLine()) != null && (q = out2.readLine()) != null){ i++; if (w.equals(q) && !w.equals("\n")){ j++; correct[j-1] = String.valueOf(i); }else { t++; wrong[t-1] = String.valueOf(i); } } File writeName3= new File("./Grade/Grade.txt"); BufferedWriter out3 = new BufferedWriter(new FileWriter(writeName3)); out3.write("Correct:" + j + "("); out3.flush(); for (int x = 0;x < j;x++){ if (x != j-1) out3.write(correct[x] + ","); else out3.write(correct[x]); out3.flush(); } out3.write(")" + "\n" + "Wrong:" + t + "("); out3.flush(); for (int x = 0;x < t;x++){ if (x != t-1) out3.write(wrong[x] + ","); else out3.write(wrong[x]); out3.flush(); } out3.write(")" + "\n"); out3.flush(); out3.close(); } }
六、功能测试
1、四则运算题目的生成
(1)指令界面
(2)题目文件
2、答案判定
(1)指令界面
(2)结果展示
做题情况: // 做错4、14、15、16题 答案:
批改情况:
七、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
60 |
50 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
15 |
Development |
开发 |
1540 |
1622 |
· Analysis |
· 需求分析 (包括学习新技术) |
50 |
30 |
· Design Spec |
· 生成设计文档 |
30 |
20 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
5 |
· Design |
· 具体设计 |
180 |
200 |
· Coding |
· 具体编码 |
1200 |
1300 |
· Code Review |
· 代码复审 |
30 |
25 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
40 |
60 |
Reporting |
报告 |
120 |
150 |
· Test Report |
· 测试报告 |
60 |
110 |
· Size Measurement |
· 计算工作量 |
30 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
10 |
合计 |
|
1750 |
1837 |
八、项目小结
遇到的难点:
(1)算式的生成:尝试了几种不同的办法,最终是通过两个子式子合成公式的方式完成了这个功能,不足之处是没有考虑子公式中包含分式的情况;
(2)分式的处理和表示:每一次式子生成时对他的运算结果进行处理并保留,在合成最终公式时再进行统一的运算和处理;
(3)相同式子的判别:找寻不到好的方法,最后是通过答案的比对和式子长度进行模糊的判断,会存在“误杀”的情况;
一些想法:
相比于第一次,感觉差别比较明显的是这次的项目更加有计划性了,虽然没有处处考虑到,但是比起写一点想一点确实顺畅了很多;在实现过程中,由于对算法的了解不够,如波兰表达式之类的进行开发时并没有相关的概念,使得查资料有所疏漏,在比较基础关键的地方是自己用“土方法”写出来的,不够完善而且比较繁琐。我和梓波都不是java的熟手,我更是第一次使用java进行开发,但好在在两个菜鸟互相监督下,这次的结对项目有惊无险的进行下来了。
梓波闪光点:梓波对于代码的测试细心,发现了不少我没有注意到的bug,并且能够找到问题的原因并改正;此外,他还有不错的执行能力,每次安排的任务都能按要求完成,出了漏洞也会第一时间修正,几处比较困难的功能,如分式的实现都是他完成的。
向宇闪光点:工作态度上积极向上,会主动揽活,不偷懒,自己能干多点他绝对不会要求别人。遇到问题也擅长与队友沟通交流,在合作期间我们基本每天都有联系,有时还通过腾讯会议开会交流。为人也谦逊,做事周到,有大局观,任务一下来他立马写文档,分配任务,一切都是那么有条理。在工作时,有想法,学习能力也不差,他本来没接触过java,可考虑到我,他也很轻松地说那就用java。总而言之,与他合作,实在是太舒服了。