一、Github项目地址
https://github.com/lyjkekeke/Arithmetic
项目成员:刘昱君,潘蓓文
二、PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
30 |
Development |
开发 |
2340 |
2565 |
· Analysis |
· 需求分析 (包括学习新技术) |
210 |
180 |
· Design Spec |
· 生成设计文档 |
90 |
120 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
60 |
30 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
60 |
45 |
· Design |
· 具体设计 |
500 |
400 |
· Coding |
· 具体编码 |
1000 |
1200 |
· Code Review |
· 代码复审 |
120 |
90 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
300 |
500 |
Reporting |
报告 |
275 |
240 |
· Test Report |
· 测试报告 |
240 |
200 |
· Size Measurement |
· 计算工作量 |
15 |
15 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
20 |
25 |
合计 |
|
2610 |
2835 |
三、效能分析
最初我们实现了随机生成题目并同时产生答案的功能,此时所花费的时间并不多,因为我们的答案使用后缀表达式生成的,所以我们考虑到如果两个式子后缀表达式相同则他的每一步运算过程都相同,通过HashSet存储后缀表达式并每生成题目就使用它的add方法若add不成功则重新生成,此时虽然所耗时间不多,但我们忘记考虑后缀表达式计算过程中+和×左右交换相同的问题,所以我们重新优化了原先的代码,在之前的基础上将后缀表达式用他的计算过程生成一种表达式来查重,只要先辨别该表达式是否一样若不同就将+或×左右交换后继续比较,这样就得到了比较准确的结果,但是这样同时将时间复杂度提升了许多倍,未来我们将进行进一步的优化
程序中消耗最大的功能:
生成10000道不重复的100内数值的四则表达式
四、设计实现过程
主类Ari_main类中调用Jfra类初始化图形界面,在图形界面中包括四个按钮以及三个文本区域:按钮对应功能分别为“生成题目”“保存文件”“上传文件”和“核对答案”,文本区域从左到右的功能分别为显示生成的题目,输入答案或显示上传的答案文件内的内容,显示答案的正确与否、题目的正答和答题情况。点击“生成题目”按钮先调用Creater类生成题目,再调用DupCheck类对生成的题目进行查重,最后调用formJudge类计算出表达式的答案;点击“保存文件”按钮将生成的题目存入一个文件,并保存于本地,同时生成对应的答案文件,存放于同一路径;点击“上传答案”按钮读取选择的文件,并将文件内的内容打印在中间的文本区域;点击“核对答案”按钮将第二个文本区域内的内容一行行读取出来,存入List当中,并将它与正确答案进行核对。
4.1该程序包含的类和函数如下图所示。
4.2以下为关键函数流程图
4.2.1生成题目(包含产生括号)
4.2.2生成答案
4.2.3查重
五、代码说明
5.1随机生成题目
//创建一个新题目 /*创建思路:首先通过随机数来确定生成的题目中有几个数字,然后通过for循环 * 循环生成2*n个随机数,这时每两个随机数可以组成一个分数,并且根据要求的 * 形式化简,接着通过随机数来判断生成的运算符从而随机生成n-1个运算符,然后将 * 这些按顺序连接成字符串返回*/ public numItem pro_creater(int range) { //创建rc,rn,ro来存储随机数从而随即得到 int rc,rn,ro; numItem str = new numItem(); String [] ch = new String[] {"+","-","×","÷"}; rc = (int)(Math.random()*3+2);//随机生成2~4的数来判断生成几个数 int [] a = new int[2*rc]; for(int i=0; i < 2*rc; i++) { a[i] = 1; } String [] num_str = new String[rc]; String [] num_str1 = new String[rc]; String [] b = new String[rc-1]; for(int j = 0; j < rc; j++) { rn = (int)(Math.random()*(2-0));//随机生成0和1判断生成真分数或整数和带分数 if(rn == 1) { a[2*j+1] = (int)(Math.random()*range+1); a[2*j] = (int)(Math.random()*(a[2*j+1]-1)+1); num_str[j] = fj.proSimple(a[2*j],a[2*j+1]); } else{ a[2*j] = (int)(Math.random()*range+1); a[2*j+1] = (int)(Math.random()*(a[2*j]-1)+1); num_str[j] = fj.improSimple(a[2*j],a[2*j+1]); } num_str1[j] = a[2*j] + "/" + a[2*j+1]; } for(int k =0; k < rc-1; k++) { ro = (int)(Math.random()*(4-0));//生成0到3的随机数来判断生成什么运算符 b[k] = ch[ro]; } //加括号 str = parent_creater(rc,num_str,num_str1,b); return str; }
5.1.1创建题目中有生成括号的功能,以下为生成括号的代码
//添加括号 /*思路:生成一个随机数来判断生成多少个括号,左括号和右括号必须成对出现,构建一个 * 最终字符串数组,一个左括号位置数组和一个右括号位置数组,如果是两个数字则不用生 * 成括号,如果是两个以上则随机生成最多n/2+1个括号,生成该范围内的随机数来判断生 * 成多少括号,先生成随机左括号的位置,然后根据左括号位置随机生成右括号位置,并将 * 两个同时放入数组,若右括号位置找出范围则停止,最后将他们和已有字符连接生成最终式子*/ public numItem parent_creater(int rc, String [] num_str, String [] num_str1, String [] b) { numItem str = new numItem(); String [] spl = new String[rc]; String [] spr = new String[rc]; for(int i = 0; i < rc; i++) { spl[i] = ""; spr[i] = ""; } int rp = (int)(Math.random()*(rc/2+1-0)); if(rc == 2) { str.newstr = num_str[0] + " " + b[0] + " " + num_str[1]; str.oldstr = num_str1[0] + " " + b[0] + " " + num_str1[1]; } else { int lmin = 0; for(int i = 0; i < rp; i++) { int rpl = (int)(Math.random()*(rc-1-lmin)+lmin);//生成左括号位置 int rpr = (int)(Math.random()*(rc-rpl-1)+rpl+1);//根据左括号位置生成右括号位置 if(rpl == 0 && rpr ==rc-1) { i--; continue; } if(rpr > rc-1) break; spl[rpl] = "("; spr[rpr] = ")"; lmin = rpr+1; } if(spl[0] == "") { str.newstr = num_str[0] + " " + spr[0]; str.oldstr = num_str1[0] + " " + spr[0]; } else if(spr[0] == "") { str.newstr = spl[0] + " " + num_str[0]; str.oldstr = spl[0] + " " + num_str1[0]; } else{ str.newstr = spl[0] + " " + num_str[0] + " " + spr[0]; str.oldstr = spl[0] + " " + num_str1[0] + " " + spr[0]; } for( int i = 1; i < rc; i++) { str.newstr = str.newstr + " " + b[i-1] + " " + spl[i] + " " + num_str[i] + " " + spr[i]; str.oldstr = str.oldstr + " " + b[i-1] + " " + spl[i] + " " + num_str1[i] + " " + spr[i]; } } return str; }
5.2 生成答案
5.2.1中缀表达式转后缀表达式
//通过后缀表达式计算数值 /* 1. 从左到右遍历表达式的每个数字和符号 * 1.1 若是数字则进栈 * 1.2 若是运算符则将栈顶两个元素出栈,进行运算并将运算结果进栈 * 2. 遍历完后缀表达式,此时栈中剩余的数字就是运算结果 */ private String calculateByPostfix(List<Item> postfixes) { Stack<String> stack = new Stack<String>(); for (Item item : postfixes) {//遍历后缀表达式 if (item.isNumber()) {//若为数则直接入栈 stack.push(item.value); } else {//若为操作符则将栈顶两个元素取出并进行相应运算 //将得到的两个字符串分数转化成Number型 String num_1,num_2; Number num1 = new Number(); Number num2 = new Number(); Number result = new Number(); num_1 = stack.pop(); num_2 = stack.pop(); num1 = returnNum(num_1); num2 = returnNum(num_2); if (item.isAdd()) {//计算加法 result.a = num2.a * num1.b + num1.a * num2.b; result.b = num2.b * num1.b; } else if (item.isSub()) {//计算减法 result.a = num2.a * num1.b - num1.a * num2.b; result.b = num2.b * num1.b; if(result.a < 0) return null;//若为负数则不生成答案 } else if (item.isMul()) {//计算乘法 result.a = num2.a * num1.a; result.b = num2.b * num1.b; } else if (item.isDiv()) {//计算除法 result.a = num2.a * num1.b; result.b = num2.b * num1.a; } else { throw new IllegalArgumentException("Operator invalid : " + item.value); } stack.push(result.a + "/" + result.b); } }
5.2.2后缀表达式计算结果
//通过后缀表达式计算数值 /* 1. 从左到右遍历表达式的每个数字和符号 * 1.1 若是数字则进栈 * 1.2 若是运算符则将栈顶两个元素出栈,进行运算并将运算结果进栈 * 2. 遍历完后缀表达式,此时栈中剩余的数字就是运算结果 */ private String calculateByPostfix(List<Item> postfixes) { Stack<String> stack = new Stack<String>(); for (Item item : postfixes) {//遍历后缀表达式 if (item.isNumber()) {//若为数则直接入栈 stack.push(item.value); } else {//若为操作符则将栈顶两个元素取出并进行相应运算 //将得到的两个字符串分数转化成Number型 String num_1,num_2; Number num1 = new Number(); Number num2 = new Number(); Number result = new Number(); num_1 = stack.pop(); num_2 = stack.pop(); num1 = returnNum(num_1); num2 = returnNum(num_2); if (item.isAdd()) {//计算加法 result.a = num2.a * num1.b + num1.a * num2.b; result.b = num2.b * num1.b; } else if (item.isSub()) {//计算减法 result.a = num2.a * num1.b - num1.a * num2.b; result.b = num2.b * num1.b; if(result.a < 0) return null;//若为负数则不生成答案 } else if (item.isMul()) {//计算乘法 result.a = num2.a * num1.a; result.b = num2.b * num1.b; } else if (item.isDiv()) {//计算除法 result.a = num2.a * num1.b; result.b = num2.b * num1.a; } else { throw new IllegalArgumentException("Operator invalid : " + item.value); } stack.push(result.a + "/" + result.b); } } //返回化简成分数或整数或带分数的结果 Number num = new Number(); String ans = stack.pop(); num = returnNum(ans); if(num.a < num.b) return proSimple(num.a,num.b); else return improSimple(num.a,num.b); }
5.3生成答案
5.3.1后缀表达式转化成通过计算过程产生的查重表达式
//将后缀表达式转化为可以查重的表达式的形式,即按照计算过程排列的表达式 /*由于运算结果时会用到后缀表达式,在这里将后缀表达式转换成可以查重的表达式 * (过程运用堆栈且基本和计算后缀表达式一的做法).思路主要是以后缀表达式的 * 长度循环,如果遇到数字就压栈,遇到运算符就连续出栈两个数字字符,出栈后将“#” * 压入数字栈,这个字符就类似计算后缀表达式时的结果,完成循环后可得到查重的表达式*/ public List<String> getDupExpression(List<Item> postfixes) { List<String> dup = new ArrayList<String>(); Stack<String> stack = new Stack<String>(); for (Item item : postfixes) { if (item.isNumber()) { stack.push(item.value); } else { String result = "#",num1,num2; dup.add(item.value); num1 = stack.pop(); num2 = stack.pop(); if(num1 != "#") { dup.add(num1); }else { dup.add("null"); } if(num2 != "#") { dup.add(num2); }else { dup.add("null"); } stack.push(result); } } return dup; }
5.3.2通过查重表达式查重
//判断两个字符串是否重复 public boolean dupCheck(String str1,String str2) { formJudge jd = new formJudge(); List<String> s1 = getDupExpression(jd.infix2postfix(jd.parse(str1)));//将字符串s1转化成字符列表 List<String> s2 = getDupExpression(jd.infix2postfix(jd.parse(str2)));//将字符串s2转化成字符列表 if(s1.equals(s2)) return true;//若两列表内容相同返回true for(int j = 0; j < s2.size()-1; j++) {//若不同进行循环将+或×后的两个字符串交换看能否相同 if(s2.get(j).equals("+")||s2.get(j).equals("×")) { String temp = s2.get(j+1); s2.set(j+1,s2.get(j+2)); s2.set(j+2,temp); } j*=3; if(s1.equals(s2)) return true;//若交换后相同则返回true } return false; }
5.4核对答案
//点击核对答案的事件 b4.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub a3.setText(""); String [] ansline = a2.getText().split("\n"); int correct = 0,error = 0; for(int i = 0; i < ansline.length ; i++) { if(anslist.get(i).equals(ansline[i])){ a3.append("√" + " " + "正答:" + " " + anslist.get(i) + '\n'); correct++; }else { a3.append("×" + " " +"正答:" + " " + anslist.get(i)+ '\n'); error++; } } a3.append("正确数目为:" + correct + '\n'); a3.append("错误数目为:" + error + '\n'); } });
六、测试运行
我们能够确定生成的答案的正确性是通过多次测试得到的,我们计算过每个式子的答案是否真正正确,也填过错误的答案来判断核对答案功能是否成功,同理,其他功能的正确性也是如此
6.1打开后进入图形界面
6.2点击生成题目
6.3点击保存文件
6.4点击上传答案
6.5点击核对答案
6.6 测试查重
七、项目小结
通过这次项目,我们对结对编程有了一个更深的了解。通过结对,在代码设计的过程中,在编程过程能够更快地找到代码中的漏洞,并且比较容易找到解决之法。当然因为双方的思考方式是不一样的,因此在讨论的过程中会出现思想的碰撞,从而拖延了项目的进度。我们了解到在合作的过程中,要多从对方的角度考虑,不要固执己见。