结对编程(Java实现)
Posted Amagic
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结对编程(Java实现)相关的知识,希望对你有一定的参考价值。
一、Github项目地址:https://github.com/qiannai/CreateArithmetic
二、PSP2.1表格:
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
·Planning |
·计划 |
30 |
40 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
20 |
·Development |
·开发 |
300 |
420 |
· Analysis |
· 需求分析 |
60 |
50 |
· Design Spec |
· 生成设计文档 |
30 |
20 |
· Design Review |
· 设计复审 |
30 |
30 |
· Coding Standard |
· 代码规范 |
100 |
120 |
· Design |
· 具体设计 |
60 |
20 |
· Coding |
· 具体编码 |
1200 |
700 |
· Code Review |
· 代码复审 |
20 |
20 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
200 |
100 |
·Reporting |
·报告 |
100 |
140 |
· Test Report |
· 测试报告 |
60 |
50 |
· Size Measurement |
· 计算工作量 |
20 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
40 |
50 |
合计 |
|
2010 |
1800 |
三、性能分析:
1、为了尽快的完成程序,目的保证是项目能够正确的完成,所以牺牲了许多时间。
例如:实现整数跟分数之间的运算,是直接将分数化成假分数去运算,而不是像3+3/4可以直接等于3’3/4 ,而是(3*4+3)/4再化成真分数运算。不过花费较多的时间去优化这一部分,虽然会快很多,代码会有很多复杂性,而且代码可读性较差。
2、对程序的分析,因为答案文档跟题目是相对应的,所以在生成题目的同时就对答案生成,采用的是左结合的方式,边生成式子边生成答案,所以在这里牺牲了空间存储。因为如果采用题目生成跟答案分开的形式,可以对用户是否输入答案做出判断,如果用户没有输入答案,则直接判错,不进行运算分析。不过这样子虽然提高了性能,但是失去了生成答案的必要性,在答案必须生成的情况下,则一边生成数的方式一边生成答案的方法可以提供很高的性能。
//创建up以内的数的四则运算 String[] createArithmetic(int up){//返回一个大小为3的String数组,str[0]保存了答案,str[1]保存查重标记,str[2]保存的是一条四则运算式子 Computed computed = new Computed(); Sum s = new Sum(); String[] str = new String[3]; Random rand = new Random(); int saveRand = rand.nextInt(4); int forNum = 0; String n1 = s.createNum(up); String n2 = s.createNum(up); switch(saveRand){ case 0: case 1: case 2:str=computed.sub(n1, n2, str);break;//做加法 case 3: case 4: case 5:str=computed.sub(n1, n2, str);break;//做减法 case 6: case 7:str=computed.multi(n1, n2, str);break;//做乘法 case 8: case 9:str=computed.div(n1, n2, str);break;//做除法 } forNum = rand.nextInt(3);//随机生成控制运算符数 for(int i = 0;i<forNum;i++){ saveRand = rand.nextInt(10); n1 = s.createNum(up); switch(saveRand){//30的机率做加减法,20的机率做乘除法 case 0: case 1: case 2:str=computed.sub(n1, "", str);break;//做加法 case 3: case 4: case 5:str=computed.sub(n1, "", str);break;//做减法 case 6: case 7:str=computed.multi(n1, "", str);break;//做乘法 case 8: case 9:str=computed.div(n1, "", str);break;//做除法 } } return str; }
3、求最大公因数,为了编程的简单,所以采用了测试的方法。但是分数的使用频率高,所以这方面花费时间高。注释为原先代码,非注释部分为采用辗转相除法改进。
int maxCommonFactor(int r1,int r2){//求出最大公因数 int num1 = r1<r2?r1:r2;//两数最小 int num2 = r1<r2?r2:r1;//两数最大 int temp=0; /*for(int i=num1;i>=1;i--){ if(0==num1%i&&0==num2%i){ return i; } } return 1;*/ //代码优化 //辗转相除法 if(num1==0)return 1;//如果分母是0,则代表该式子是被整除的,直接返回 if(num2%num1==0){ return num1; }else{ temp=num2; num2=num1; num1=temp%num1; } return maxCommonFactor(num1, num2);//递归查询 }
生成10000条程序所花费的时间
4、查重是一个大问题,因为你比较是否重复需要对之前的生成式子进行一个个的比较。这个地方没有想到改善的方法,该查重的时间复杂度为n*(n-1)/2。为整个程序中运行时间最久的。
while(i<n){ b = true; j++; System.out.println(j);//留待测试那些相同的等式判别。 str =Arithmetic.this.createArithmetic(r); for(String s:str_1){ if(str[1].equals(s)){//如果已经有了则设置为false b=false; } } if(b){//如果为false,则代表生成的式子是重复的所以不录入 str_1[i]=str[1]; try { jta.append("文件正在写入中\\n"); fw_1.write("四则运算题目 " + (i+1) + ": " + str[2]+ " = " + "\\r\\n"); fw_2.write("四则运算答案 " + (i+1) + ": " + str[0]+ "\\r\\n"); fw_1.flush(); fw_2.flush(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } i++; } }
四、设计实现过程:
1、对式子查重的看法:
(1)拿到这道题觉得是比较难的,看完题的第一印象就是先不管运算结果,先随机生成一条运算式子,再通过一种方法对整一条式子进行计算。但是我看到第六点的时候,要求必须要生成的式子必须要不同,我想到了标记的方法,对每一条式子做出标记。我想的标记是用运算的顺序进行标记。所以我改选其他方法生成四则运算。通过一个String[]数组保存一条式子的结果,运算顺序,运算式。结果可以用来生成结果集,运算顺序用来判断是不是两条式子是否相同,运算式用来作为题集。
(2)类似:1+2跟 2+1 是相同。对运算方法标记为1 2+。对式子排序标记。如果生成的为1和2做了加法,跟生成的为2和1做加法,同理其标记式为相同。
(3)一开始觉得1+2+3跟1+3+2是一条相同的式子,就有对每一次做加法的时候判断上一次是否做了加法(取上一次的运算顺序的最后一个符号)对整条式子进行排序。发现做法很麻烦,想着用一开始的方法去做,(即随机生成一条式子,再通过式子计算结果)然后用结果作为标识符,只有出现不同的结果才录入。不过觉得去除的式子太多了。(后面还是想到了一种很好的方法,就是将所有的式子所有的运算数排序,先比较结果,结果一样,再比较运算数的排序,一样则去除式子)(然而已经做完了,所以就不去用这个方法了,可以把运算符也进行排序,做第三次判断。个人认为是比较好的方法,因为在这种判断之下,1+2+3跟3+2+1也可以去除,虽然按照左结合的方法,是不同的式子)
(4)因为判断是左结合的方法,则1+2+3跟3+2+1运算顺序保存形式分别为1 2+ 3+跟 2 3+ 1+两种标志,比较结果应该是不同的。在生成1+2+3跟3+(1+2)的时候,因为使用的是左结合的方式生成式子,在Arithmetic中的createNum()生成的加法会将+左右两端随机调换,故两式的运算顺序标记都是1 2+ 3+,如果同时出现在后续的判断中会将这一类的式子剔除。因为式子是左结合的,所以只要运算顺序不一样,则必定是两条不同的式子。
2、具体思路:
对查重有了正确的想法就需要确定式子具体的生成和对整个项目的实现。在这里我就采用了自底向上跟自顶向下的方法对整个项目进行架构。使用生成一条正确的式子和答案和运算顺序作为分界点,自底向上的最终结果是生成一条完整式子,自顶向下是外部用户界面到对一条式子的使用。
3、自底向上的开发:
(1)针对的是对一条式子的生成,生成一条式子,需要对式子进行计算。一条四则运算根据运算顺序为左结合的方式,左结合的方式为对一个运算符两端进行计算,即每次都是对两个数进行计算,所以要计算一条式子,首先要完成两个数之间的运算。
(2)具体可以分为整数跟整数的运算,整数跟分数的运算,分数跟分数的运算。运算包括了加减乘除。所以我建立了一个类Sum对写了12个运算,分别为:整数跟整数的加法,整数跟分数的加法,分数跟分数的加法……
(3)实现了12个计算后,如果直接使用其实是比较麻烦的,因为要对输入的数进行整数分数判断。所以必须对其整合成数跟数的计算。建立一个add_nan(String n1,String n2)方法对n1跟n2两个数进行判断两个数是分数还是整数,判断后调用相对应的计算方法,同理,其他的运算符也是如此。
(4)对于含分数的加法,其实可以直接的使用带分数的整数跟整数相加减,分数跟分数相加减。虽然这是正常的计算方式,但是对于计算来说,需要对数进行判断是比较麻烦的。所以直接将分数化为假分数,通过运用假分数进行计算,最后将假分数化为真分数就可以实现分数之间的运算。
(5)对于减法有点的不同,必须要相减为正数,所以在做减法的时候通过建立is1max2(String r1,String r2)的方法判断两个数是否r1>r2,结果为返回减法后的绝对值。
在这里就实现了数与数之间的算术。
Sum类中包含了{12个整整,整分,分分之间的加减乘除,4个数与数之间的加减乘除,1个将分数化为假分数,1个假分数化为真分数,1个计算最大公因数,1个产生一个随机分数或整数的方法}
在这里就构成了底层函数,形成计算结果,这为产生一个四则运算式的一期工作。
(6)这只是计算式子答案,在Computed类中建立了add,sub,multi,div四个方法,调用Sum中的计算方法得出结果,在方法中统一用(String r1, String r2, String[] t)的接口。
r1为每次生成的左结合随机数,如果t[0]为空,则代表是第一次计算,其进行计算的随机数为r1跟r2两个随机数,如果t[0]不为空,则证明是进行了第二次计算。
其中t[]为三个String组成的数组,t[0]代表上一次计算的结果,t[1]代表上一次计算的顺序,t[2]代表上一次的运算式子。
通过输入生成对应的式子。在Arithmetic类中有一个String[] createArithmetic(int up)方法,为创建一条四则运算的式子,up表示生成数的范围。up传参到CreateNum方法中生成随机数。通过随机选取运算方式生成一条四则运算式子。这就完成了第二期任务。
注:因为有了上一次个人设计的经验,在这里中,提前做好了接口规范,一开始就用String类型保存数字。对于同类型的方法统一用同样的数字类型。所以在后续的合成一条式子中,用t数组保存了,生成答案的t[0],用于判断是否重复的t[1],用于生成题集的t[2]。保证了后续的使用不用回来修改接口的问题,节省大量时间的代码优化。
4、生成一条四则运算的式子后,应该要从用户进入界面入手,一步步到使用生成的四则运算式子。开始设计用户使用界面:
(1)命令行的输入,做成可视化界面后,对用户的输入用JTextField的一行输入,对于各种错误还有运行的信息用一个文本框来输出提示,提示的文本框并非给用户编辑的。对于输入的命令需要设置监视器,用一个按钮对命令输入框进行监控。
(2)运行完-n和-r的命令之后,将生成一个问题的txt文件和一个保存题目答案的文件。对于这两个文件用两个按钮绑定,点击按钮会打开生成的问题或者是生成答案的文件。
(3)对做完的题目需要生成成绩,设置一个计算成绩的按钮,点击按钮将读取问题的答案和标准答案进行比较,因为无法预计用户对计算成绩的操作进行乱输入,设置成按钮,保证可以计算的是同一份文件的成绩。
(4)同样可以点击打开成绩按钮查看保存为txt文件的计算后的成绩。
(5)为了用户的更容易使用,文件的保存位置可以是自定义的,所以代码中不保存保存路径,需要从用户获取,建立一个visualGetPath()方法,打开一个用户路径获取的可视化界面。
该方法为程序一开始运行弹出,配置完成后,才调用visual()方法,打开用户使用界面。对于用户输入的路径错误,则设立了一个按钮用于修改保存路径,当点击按钮,调用visualGetPath()方法,再点击完成,关闭上一个用户使用界面,打开新的用户使用界面,如果点击返回,则不做任何变化。
(6)设置一个软件关闭按钮,点击按钮,将关闭所有的程序。
(7)设置一个帮助按钮,点击按钮弹出用户使用帮助框。
五、代码说明:以加法为例,其他运算符的运算方式与加法运算相似
1、两个数的加法,对不同的输入进行区分,调用对应的办法。
public String add_nan(String n1,String n2){//两个数相加的值 String sum = ""; if(Pattern.matches(".+\\\\/.+", n1)){//n1是分数 if(Pattern.matches(".+\\\\/.+", n2)){//n1是分数,n2是分数 sum = this.add_faf(n1, n2); }else{//n1是分数,n2是整数 sum = this.add_faz(n1, n2); } }else{//n1是整数 if(Pattern.matches(".+\\\\/.+", n2)){//n1是整数,n2是分数 sum = this.add_faz(n2, n1); }else{//n1是整数,n2是整数 sum = this.add_zaz(n1, n2); } } return sum; }
2、整数跟整数加法
public String add_zaz(String z1,String z2){//两个整数相加 String sum =""; int s = 0; int i1 = Integer.parseInt(z1); int i2 = Integer.parseInt(z2); s = i1 + i2; sum+=s; return sum; }
3、分数跟分数加法
public String add_faf(String r1,String r2){//两个分数相加 int[] i1 = new int[2]; int[] i2 = new int[2]; int[] s = new int[2]; String sum =""; i1 = this.falseFaction(r1); i2 = this.falseFaction(r2); s[0]=i1[0]*i2[1]+i2[0]*i1[1]; s[1]=i1[1]*i2[1]; sum = this.trueFaction(s); return sum; }
4、分数跟整数加法
public String add_faz(String f,String z){//分数与整数相加,返回一个String和 String sum = ""; int[] i1 = new int[2]; int i2 = Integer.parseInt(z); i1 = this.falseFaction(f); i1[0] = i2*i1[1]+i1[0]; sum = this.trueFaction(i1); return sum; }
5、化为真分数
String trueFaction(int[] flaFac){//化为真分数 String s=""; int k = 0; int fenzi = 0; int maxCom =1; fenzi =flaFac[0]%flaFac[1];//如果是整除,则为0 maxCom = this.maxCommonFactor(fenzi, flaFac[1]);//分数之间最大公因数 k=flaFac[0]/flaFac[1];//整除,则k为整数 fenzi/=maxCom; flaFac[1]/=maxCom; if(fenzi==0){ s+=k; }else{ if(k==0){ s+=fenzi+"/"+flaFac[1]; }else{ s+=k+"\'"+fenzi+"/"+flaFac[1]; } } return s; }
6、化为假分数
int[] falseFaction(String f){//将一个分数换成分子除分母的int型,0为分子,1为分母 String[] cutF =new String[3]; int[] fract = new int[3]; int[] falseFac = new int[2]; if(Pattern.matches(".*\\\\\'.*", f)){ cutF = f.split("\\\\/|\\\\\'"); for(int i=0;i<cutF.length;i++){ fract[i]=Integer.parseInt(cutF[i]); } falseFac[0]=fract[0]*fract[2]+fract[1]; falseFac[1]=fract[2]; }else{ cutF = f.split("\\\\/"); for(int i=0;i<cutF.length;i++){ fract[i]=Integer.parseInt(cutF[i]); } falseFac[0]=fract[0]; falseFac[1]=fract[1]; } return falseFac; }
7、求最大公因数
int maxCommonFactor(int r1,int r2){//求出最大公因数 int num1 = r1<r2?r1:r2;//两数最小 int num2 = r1<r2?r2:r1;//两数最大 int temp=0; /*for(int i=num1;i>=1;i--){ if(0==num1%i&&0==num2%i){ return i; } } return 1;*/ //代码优化 //辗转相除法 if(num1==0)return 1;//如果分母是0,则代表该式子是被整除的,直接返回 if(num2%num1==0){ return num1; }else{ temp=num2; num2=num1; num1=temp%num1; } return maxCommonFactor(num1, num2);//递归查询 }
8、产生子式子(生成答案,用于查重标记,式子)
//t数组的值为"结果"+"排序"+"格式" //r1为随机值整数,r2为随机整数 String[] add(String r1,String r2,String[] t){ String[] ans = new String[3]; //返回值,0="结果";1="排序";2="格式" if(t[0]!=null){//短式的加法 ans[0] = s.add_nan(r1, t[0]); if(Pattern.matches(".+(\\\\+|\\\\-)", t[1])){ saveRand = rand.nextInt(100); if(saveRand>=70){//r1 =1;t[0]=2+3;30的概率做1+(2+3)的变换,70的概率为2+3+1 ans[2] = r1 + " " + "+" + " " + "(" + t[2] + ")"; }else{ ans[2] = t[2] + " " + "+" + " " + r1; } }else{//如果是乘法则不需要加括号,直接交换顺序 saveRand = rand.nextInt(100); if(saveRand>=70){ ans[2] = r1 + " " + "+" + " " + t[2]; }else{ ans[2] = t[2] + " " + "+" + " " +r1; } } ans[1] =t[1] + " " + r1 + "+"; }else{//r2与r1 的加法 ans[0] = s.add_nan(r1, r2); if(s.is1max2(r1, r2)){//标识为高到低 ans[1]=r1 + " " + r2 + "+"; }else{ ans[1]=r2 + " " + r1 + "+"; } ans[2]=r1 + " " + "+" + " " + r2; } return ans; }
9、随机生成式子
//创建up以内的数的四则运算 String[] createArithmetic(int up){//返回一个大小为3的String数组,str[0]保存了答案,str[1]保存查重标记,str[2]保存的是一条四则运算式子 Computed computed = new Computed(); Sum s = new Sum(); String[] str = new String[3]; Random rand = new Random(); int saveRand = rand.nextInt(4); int forNum = 0; String n1 = s.createNum(up); String n2 = s.createNum(up); switch(saveRand){ case 0: case 1: case 2:str=computed.sub(n1, n2, str);break;//做加法 case 3: case 4: case 5:str=computed.sub(n1, n2, str);break;//做减法 case 6: case 7:str=computed.multi(n1, n2, str);break;//做乘法 case 8: case 9:str=computed.div(n1, n2, str);break;//做除法 } forNum = rand.nextInt(3);//随机生成控制运算符数 for(int i = 0;i<forNum;i++){ saveRand = rand.nextInt(10); n1 = s.createNum(up); switch(saveRand){//30的机率做加减法,20的机率做乘除法 case 0: case 1: case 2:str=computed.sub(n1, "", str);break;//做加法 case 3: case 4: case 5:str=computed.sub(n1, "", str);break;//做减法 case 6: case 7:str=computed.multi(n1, "", str);break;//做乘法 case 8: case 9:str=computed.div(n1, "", str);break;//做除法 } } return str; }
10、重复式子去除
int i =0; String[] str_1 = new String[n];//创建大小为n条式子的存储空间,用于保存查重 boolean b = true; int j=0; while(i<n){ b = true; j++; System.out.println(j);//留待测试那些相同的等式判别。 str =Arithmetic.this.createArithmetic(r); for(String s:str_1){ if(str[1].equals(s)){//如果已经有了则设置为false b=false; } } if(b){//如果为false,则代表生成的式子是重复的所以不录入 str_1[i]=str[1]; try { jta.append("文件正在写入中\\n"); fw_1.write("四则运算题目 " + (i+1) + ": " + str[2]+ " = " + "\\r\\n"); fw_2.write("四则运算答案 " + (i+1) + ": " + str[0]+ "\\r\\n"); fw_1.flush(); fw_2.flush(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } i++; } }
六、测试运行:
1、获取工作路径,如果一开始没有获得路径,不能返回,路径正确后完成则进入程序运行界面。默认路径为D:/
2、输入-n 10000后输入-r 10 ,获取生成条数,生成数的范围
3、运行后生成题目,和答案,点击打开问题或者答案可以直接打开对应文件
4、完成题目答案后,点击check Answer可以对文件打分,并生成答案文档,不过需要先对撰写答案后的题目文件进行保存后运行。点击open Grade可以打开查看成绩。在第四五题填写正确答案,第七题填错误答案,第674,8016,8017题填写正确答案,第8742题填写错误,第9997题填写正确,其余不填写
5、编写答案文件,并检查用例
6、对计算的测试代码
Sum s = new Sum(); String[][] str = {{"4","9"},{"3/4","5/9"},{"7","4/7"},{"4/9","3"},{"2\'3/4","5\'3/7"},{"3","4\'2/3"},{"2\'3/5","3"},{"3\'2/3","2/3"}}; //保存{整数跟整数,分数跟分数,整数跟分数,分数跟整数,带分数跟带分数,整数跟带分数,带分数跟整数,带分数跟分数} for(String[] sl:str){ System.out.println(sl[0] + "+" + sl[1] + " : " + s.add_nan(sl[0], sl[1])); System.out.println(sl[0] + "*" + sl[1] + " : " + s.multi_nan(sl[0], sl[1])); System.out.println("|" + sl[0] + "-" + sl[1] + "|" + " : " + s.sub_nan(sl[0], sl[1])); System.out.println(sl[0] + " / " + sl[1] + " : " + s.div_nan(sl[0], sl[1])); System.out.println(); } System.out.println("4跟9:"+s.maxCommonFactor(4, 9)); System.out.println("3跟9:"+s.maxCommonFactor(3, 9)); System.out.println("15跟9:"+s.maxCommonFactor(15, 9)); int[] flaFac={27,18}; System.out.println("27/18化为真分数:"+s.trueFaction(flaFac));
7、运行结果。
子代码运行计算正确,并且由上面生成产生子式子,与5、6中的代码可以看出运行的正确性,并且计算遵循左结合的方式进行。
8、重复代码去除
从运行结果可以看出程序产生了12067条式子,实际生成式子为10000条。由代码分析那里有具体查重分析,去除掉了2000条重复代码,在去重的代码中插入了在while语句中检查重复多少次的计数器j,并将之在控制台输出
9、计算过程中不产生负数
String[] sub(String r1,String r2,String[] t){ String[] ans = new String[3]; //返回值,0="结果";1="排序";2="格式" if(t[0]!=null){//短式的减法 ans[0] = s.sub_nan(r1,t[0]); if(Pattern.matches(".+(\\\\+|\\\\-)", t[1])){//如果上一次做的是加减变换需要加括号 if(s.is1max2(r1, t[0])){ ans[2]= r1 + " " + "-" + " " + "(" + t[2] + ")"; }else{ ans[2]= t[2] + " " + "-" + " " + r1; } }else{ if(s.is1max2(r1, t[0])){//r1大于短式 ans[2]= r1 + " " + "-" + " " + t[2]; }else{ ans[2]=t[2] + " " + "-" + " " +r1; } } ans[1] = t[1] + " " + r1 + "-"; }else{//r1跟r2的减法 ans[0] = s.sub_nan(r1,r2); if(s.is1max2(r1, r2)){//r1>r2 ans[1] = r1 + " "+ r2 +"-"; ans[2] = r1 + " " + "-" + " " + r2; }结对编程项目总结