项目成员:陈锐滨 3117004650 甘永强 3117004651
一、Github地址:https://github.com/NuotaSuo/partner-work
二、PSP2.1表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
40 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
40 |
30 |
Development |
开发 |
1760 |
1665 |
· Analysis |
· 需求分析 (包括学习新技术) |
80 |
125 |
· Design Spec |
· 生成设计文档 |
30 |
30 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
40 |
45 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
20 |
· Design |
· 具体设计 |
60 |
80 |
· Coding |
· 具体编码 |
1400 |
1200 |
· Code Review |
· 代码复审 |
30 |
45 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
100 |
120 |
Reporting |
报告 |
110 |
150 |
· Test Report |
· 测试报告 |
60 |
80 |
· Size Measurement |
· 计算工作量 |
20 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
40 |
合计 |
|
1910 |
1845 |
三、效能分析
程序生成10000条10以内的表达式花费时间1809ms,由于10以内数字重复概率会大一点,所以重新生成的表达式的次数会多点,多次利用的生成表达式方法导致生成时间变长,加上本次设计的时候,生成表达式的方法没有封装,所以会导致效能变低,这是需要改进的地方,但最后由于偷懒就没有去改进,因为一开始以为这个时间挺快的,等到要交博文的时候看别人比我们快多了,有点尴尬,下次要完善全部。
四、设计实现过程
我们设计了六个类。tiMu类,负责程序的接口、对表达式的不重复生成、运行时间等;
NiBolan类,负责运算表达式的结果;
Handle类,对分数的相关处理,完成真假分数的转换;
Stack类,自己定义的栈,供NiBolan类使用;
FileSave类,生成题目的Exercises.txt文件与答案的Answers.txt文件;
FileOpen类,对题目文件与答案文件进行正误判断并输出到Grade.txt文件。
五、代码说明
栈类:
首先是利用java中LinkedList
分数处理类:
由于要求四则运算中要有分数参加并且分数必须为真分数(带分数),故专门弄个类来对分数进行处理,因为式子出现的分数和计算结果都必须是真分数,所以首先要实现的功能就是化简,化简方法huajian()是将分子和分母都单独提出来然后找出他们的最大公因子,然后利用最大公因子将假分数化为真分数或者带分数并返回字符串结果,同时在这个类中还有其他几种方法:随机生成一个真分数或者带分数zhenfenshu()方法、判断是否是带分数panduan()方法、判断是否是分数方法panduan1()、将带分数转换为假分数方法fenshu(),因为我们实现的四则运算式子计算结果的方法在计算过程中是利用假分数进行计算的,所以需要判断是不是带分数,如果是则转换成假分数。然后其它就是假分数的四则计算方法了,下面是具体功能代码。
public class Handle { public String huajian(int a,int b) {//对分数进行化简 int c=1; for(int i=a;i>1;i--) { if(a%i==0&&b%i==0) { c=i; break; } } int d=a/c; int e=b/c; if(d==0) { return "0"; } if(e==1) return d+""; else return panduan0(d,e); } public String panduan0(int a,int b) { if(a>b) { int c; c=a/b; int d; d=a%b; if(d==0) return c+""; else return c+"’"+d+"/"+b; }else return a+"/"+b; } public String zhenfenshu(int m) {//随机生成一个真分数 Handle h=new Handle(); Random r=new Random(); int a=r.nextInt(m-1)+1; int b=r.nextInt(m-1)+1; String e=h.huajian(a, b); return e; } public boolean panduan(String s) {//判断是否是带分数 if(s.indexOf("’")!=-1&&s.indexOf("/")!=-1) { return true; } return false; } public boolean panduan1(String s) {//判断是否是分数 if((s.indexOf("/"))!=-1) { return true; } return false; } public String fenshu(String s) {//将带分数转换为假分数 String[] a=s.split("’"); String b=a[1]; String[] c=b.split("/"); int sum; int a1=Integer.parseInt(a[0]); int c1=Integer.parseInt(c[0]); int c2=Integer.parseInt(c[1]); sum=a1*c2+c1; return sum+"/"+c2; }
逆波兰类:
这个逆波兰类是利用逆波兰表达式即后缀表达式来让计算机计算四则运算表达式的,因为计算机看不懂中缀表达式,所以在这个类的creat(m,n)方法中先获取中缀表达式,然后转换成后缀表达式,后缀表达式的计算就需要利用到之前我们自己定义的栈了,利用栈“先进后出”的特性结合条件语句对四则运算表达式进行计算,计算的时候可能会有整数与分数相加减乘除,整数与整数相加减乘除还有分数与分数相加减乘除四种情况在下面代码中都有分开进行计算,代码如下。
/* * 由上面得到的中缀表达式转换为后缀表达式 * 1.遇到数字就输出 * 2.遇到优先级高就进栈 * 3.当准备进栈的运算符遇到优先级较高栈顶运算符优先级时,栈顶就出栈,出完栈,准备进栈的就进栈 * 4.最后全部出栈 */ public ListhouZhui(List list){ List hzlist=new ArrayList ();//用于存放后缀表达式 Stack s=new Stack();//用于存放操作符 for (int i = 0; i < list.size(); i++) { String t=list.get(i); String t1=""; if(t.equals("(")) { s.myAdd(t); }else if(t.equals("*")||t.equals("÷")) { if(!s.isNull()) { t1=(String) s.myGet1(); if(t1.equals("*")||t1.equals("÷")) { hzlist.add(s.myGet()); s.myAdd(t); }else { s.myAdd(t); } }else { s.myAdd(t); } }else if(t.equals("+")||t.equals("-")) { if(!s.isNull()) { while(!s.myGet1().equals("(")) { hzlist.add(s.myGet());//来的操作符优先级比栈顶操作符优先级高,栈顶操作符出栈 if(s.isNull()) { break; } } s.myAdd(t); }else { s.myAdd(t); } }else if(t.equals(")")){ while(!s.myGet1().equals("(")) { hzlist.add(s.myGet()); } s.myGet(); }else { hzlist.add(t); } if(i==list.size()-1) { while(!s.isNull()) { hzlist.add(s.myGet()); } } } return hzlist; } /* * 当后缀式表示出来,剩下的就是对后缀式进行计算了 */ public String jiSuan(List list) { Stack s=new Stack(); Handle h=new Handle(); String t2=""; for (int i = 0; i < list.size(); i++) { String t=list.get(i); String t1=""; //int x=0; if(!isOp(t)) { if(h.panduan(t)) { t1=h.fenshu(t); s.myAdd(t1); }else if(h.panduan1(t)) { s.myAdd(t); }else { //x=Integer.parseInt(t); s.myAdd(t); } }else { if(t.equals("+")) { String a1= s.myGet(); String a2= s.myGet(); int sum; String c=""; if(h.panduan1(a1)&&!h.panduan1(a2)) { String[] b=a1.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a2); sum=b2*b3+b1; c=sum+"/"+b2; s.myAdd(c); }else if(!h.panduan1(a1)&&h.panduan1(a2)) { String[] b=a2.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a1); sum=b2*b3+b1; c=sum+"/"+b2; s.myAdd(c); }else if(h.panduan1(a1)&&h.panduan1(a2)) { c=h.jiafa(a1, a2); s.myAdd(c); }else { int b1=Integer.parseInt(a1); int b2=Integer.parseInt(a2); sum=b1+b2; c=String.valueOf(sum); s.myAdd(c); } }else if(t.equals("-")) { String a1= s.myGet(); String a2= s.myGet(); int sum; String c=""; if(h.panduan1(a1)&&!h.panduan1(a2)) { String[] b=a1.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a2); sum=b3*b2-b1; c=sum+"/"+b2; s.myAdd(c); }else if(!h.panduan1(a1)&&h.panduan1(a2)) { String[] b=a2.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a1); sum=b1-b2*b3; c=sum+"/"+b2; s.myAdd(c); }else if(h.panduan1(a1)&&h.panduan1(a2)) { c=h.jianfa(a1, a2); s.myAdd(c); }else { int b1=Integer.parseInt(a1); int b2=Integer.parseInt(a2); sum=b2-b1; c=String.valueOf(sum); s.myAdd(c); } }else if(t.equals("*")) { String a1= s.myGet(); String a2= s.myGet(); int sum; String c=""; if(h.panduan1(a1)&&!h.panduan1(a2)) { String[] b=a1.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a2); sum=b1*b3; c=sum+"/"+b2; s.myAdd(c); }else if(!h.panduan1(a1)&&h.panduan1(a2)) { String[] b=a2.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a1); sum=b3*b1; c=sum+"/"+b2; s.myAdd(c); }else if(h.panduan1(a1)&&h.panduan1(a2)) { c=h.chengfa(a1, a2); s.myAdd(c); }else { int b1=Integer.parseInt(a1); int b2=Integer.parseInt(a2); sum=b1*b2; c=String.valueOf(sum); s.myAdd(c); } }else if(t.equals("÷")) { String a1= s.myGet(); String a2= s.myGet(); int sum; String c=""; if(h.panduan1(a1)&&!h.panduan1(a2)) { String[] b=a1.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a2); sum=b2*b3; c=sum+"/"+b1; s.myAdd(c); }else if(!h.panduan1(a1)&&h.panduan1(a2)) { String[] b=a2.split("/"); int b1=Integer.parseInt(b[0]); int b2=Integer.parseInt(b[1]); int b3=Integer.parseInt(a1); sum=b3*b2; c=b1+"/"+sum; s.myAdd(c); }else if(h.panduan1(a1)&&h.panduan1(a2)) { c=h.chufa(a1, a2); s.myAdd(c); }else { int b1=Integer.parseInt(a1); int b2=Integer.parseInt(a2); String sum1=b2+"/"+b1; s.myAdd(sum1); } } } } t2=s.myGet(); if(h.panduan1(t2)) { String[] b=t2.split("/"); int a1=Integer.parseInt(b[0]); int a2=Integer.parseInt(b[1]); String v=h.huajian(a1, a2); return v; }else { return t2; } }
生成题目类:
在这个类中我们设计了随机生成一个至三个运算符的四则运算表达式,题目生成思想是左结合方式,即依次生成子表达式结合起来,应项目要求,子表达式结果不能为负,生成的式子不能重复,于是我们设计在随机生成子表达式的时候就进行判断如果结果为负则重新生成,同时我们将每次生成的子表达式和其结果放在两个数组里面用于查重判断,即每次生产的子表达式结果会跟之前生成的式子结果比较,如果相同说明两条式子有可能是一样的,接着判断子表达式的符合,如果符合还是一样说明子表达式一样的概率很高,最后判断他们的数字是不是一样的,如果一样就重新生成式子直到数字不一样,然后括号的生成这个功能做得很粗糙,是需要改善的地方,一条含有三个运算符的式子括号的位置是有十多种情况的,这里偷懒了一下只生成了五种情况,最后生成的完整式子是由几个运算符组成的情况我们用了条件语句来进行选择,即然一个随机数去决定。一个两个三个运算符的式子出现的概率是一样的。
下面代码为避免重复生成相同的表达式而实现的查重功能代码:
//避免重复的相关代码 for (int i = 0; i < n; i++) { int order = r.nextInt(4); str3=String.valueOf(r.nextInt(m-1)+1); j=r.nextInt(3); x=r.nextInt(4); y=r.nextInt(3); str=t.ziBiaoDaShi(m); s1=N.zhongZhui(str); s2=N.houZhui(s1); result=N.jiSuan(s2); strArray[i]=result; strArray1[i]=str; for(int k=i;k>0;k--) { //开始第二次循环时就要开始判断子表达式是否一样,若一样重新生成,从而实现查重的功能, //因为题目生成是左结合,只有最左边的子表达式不一样,那整条式子就不一样 if(result==strArray[k-1]) { //结果先比对,一样进行下一步,不一样跳过 if(t.panDuanFuHao(str, strArray1[k-1])) { a[0]=t.qieGe(str); a[1]=t.qieGe1(str); a[2]=t.qieGe(strArray1[k-1]); a[3]=t.qieGe1(strArray1[k-1]); while((a[0]==a[2]&&a[1]==a[3])||(a[0]==a[3]&&a[1]==a[2])) { str=t.ziBiaoDaShi(m); s1=N.zhongZhui(str); s2=N.houZhui(s1); result=N.jiSuan(s2); strArray[i]=result; strArray1[i]=str; a[0]=t.qieGe(str); a[1]=t.qieGe1(str); a[2]=t.qieGe(strArray1[k-1]); a[3]=t.qieGe1(strArray1[k-1]); } } } }
FileSave类:调用该类时传入题目表达式的数组,对表达式NiBolan类进行运算,并将结果规范输出到Answers.txt,表达式则规范输出到Exercises.txt。
FileOpen类:该类需要传入两个String参数,分别是题目文件与答案文件对路径,通过运算题目中的表达式的值与答案文件的值相比较,可得到答案文件中答案正确与错误的数目。
六、测试运行
①用户界面:
②生成题目:
③题目答案比对:
1.对-T命令生成对答案文件和题目文件进行比对:
2.修改答案文件进行比对:
④题目生成文件:
⑤答案生成文件:
七、项目小结
本次项目对实现是由两个人实现的,相比较第一次一个人做的情况下有趣不少,第一次感觉到合作完成的快感,遇到难题可以一起讨论,一起想办法解决,比一个人的情况效率高了不少,唯一的意外我(陈锐滨)刚好遇到家里有事所以请假了一段时间,这段时间我们的合作都是通过线上进行的,也由一个写代码然后发给另一个人审阅,互相交换进行,总之合作还是挺愉快,不过效率肯定不如线下交流要好,所以回来后重新一起讨论了所有的代码功能实现并完善代码,期待下一个团队作业。本次项目我们还是有很多没完善的地方,比如括号的生成问题,代码的效率分析问题,其实代码还是有很多的改进空间的,比如对常用到方法封装起来,减少for循环次数,减少集合和数组的数量降低代码的复杂度等,下次一定要更好的完善,写出效能更好的代码功能。