结对项目第一周总结

结对项目——四则运算 阶段性总结

一、需求分析(第一周达成):

  • 能够生成n道四则运算题,n可由使用者输入来控制
  • 支持整数
  • 支持分数
  • 生成题目中含有括号
  • 可以判断正误,如果错误会输出正确答案
  • 统计正确率

扩展需求:

  • 生成题目后存入文件
  • 完成题目后从文件读入并进行判断
  • 支持题目去重
  • 支持繁體中文简体中文English

二、设计思路(同时输出UML类图):

以下是程序主体设计思路,各步骤具体操作详情请见注释:
由于本周可用的准备时间较长,我们选择直接进行真分数的操作。在java语言中并没有一个专门的类用来表示分数,所以需要手动添加。我们设计了一个专门的类用来描述分数(程序中的Fraction类),并且严格依照分数的性质来进行设计(例如分母不可以为0),其次我们要给出分数四则运算的计算方法。然后我们考虑的是,进行四则运算,要随机生成题目,我们选择分别建两个类,一个用来生成操作数(分数随机生成两个数字,分别做分子和分母),一个用来生成符号,生成的随机性我们通过random()来保证。写好了这两个类之后,我们用另一个专门生成题目的类来调用这两个类中的方法组成题目。转后缀码后进行计算。最后统计结果。

三、实现过程中的关键代码解释:

实现分数代码:

 1 public class Fraction {
 2 
 3     private long Numerator; // 分子
 4     private long Denominator; // 分母
 5 
 6     public Fraction(long Numerator, long Denominator) {
 7         this.Numerator = Numerator;     //将传过来的参数Numerator赋值给类中的Numerator
 8         if (Denominator == 0) {
 9             throw new ArithmeticException("分母不能为零");     //如果分母为0,抛出问题“分母不能为零”
10         } else {
11             this.Denominator = Denominator;       //如果分母不为0,将传过来的参数Denominator赋值给类中的Denominator
12         }
13         change();   //对分数进行化简
14     }
15 
16     public Fraction() {              //将分数初始化
17         this(0, 1);
18     }
19     public long getNumerator() {     //返回分子Numerator
20         return Numerator;
21     }
22     public void setNumerator(long numerator) {
23         Numerator = numerator;       //设置分子,将传过来的参数numerator赋值给类中的Numerator
24     }
25     public long getDenominator() {
26         return Denominator;       //返回分母Denominator
27     }
28     public void setDenominator(long denominator) {
29         Denominator = denominator;       //设置分母,将传过来的参数denominator赋值给类中的Denominator
30     }
31     private Fraction change() {
32         long gcd = gcd(Numerator,Denominator);    //求分子和分母的最大公因子并将公因子的值赋给gcd
33         Numerator /= gcd;           //将分子Numerator的值除以gcd的值赋给Numerator
34         Denominator /= gcd;           //将分母Denominator的值除以gcd的值赋给Denominator
35         return this;
36     }
37     private long gcd(long a, long b) {    //求a和b的最大公因数
38         long mod = a % b;
39         if (mod == 0) {
40             return b;          //如果余数mod为0,返回除数即为最大公因数
41         } else {
42             return gcd(b, mod);   //如果余数mod不等于0,将除数b看做被除数,把余数看做除数递归调用gdc(b,mod)
43         }
44     }
45     public Fraction add(Fraction second) {       //一个分数加一个分数的加法法则
46         return new Fraction(this.Numerator * second.Denominator + second.Numerator * this.Denominator,
47                 this.Denominator * second.Denominator);
48     }
49 
50     public Fraction minus(Fraction second) {      //一个分数减一个分数的减法法则
51         return new Fraction(this.Numerator * second.Denominator - second.Numerator * this.Denominator,
52                 this.Denominator * second.Denominator);
53     }
54 
55     public Fraction multiply(Fraction second) {          //一个分数乘一个分数的乘法法则
56         return new Fraction(this.Numerator*second.Numerator,
57                 this.Denominator * second.Denominator);
58     }
59 
60     public Fraction divide(Fraction second) {       //一个分数除以一个分数的除法法则
61         return new Fraction(this.Numerator*second.Denominator,
62                 this.Denominator * second.Numerator);
63     }
64 
65     public String toString() {     //讲分数以字符串的形式返回
66         if(this.Denominator==1){
67             return String.format("%d",this.Numerator);     //如果分母为1,输出分子
68         }
69         else {
70             return String.format("%d/%d", this.Numerator, this.Denominator);     //如果分母不为1,输出"分子/分母"
71         }
72     }
73 
74 }

生成问题代码:

 1 import java.util.*;
 2 
 3 public class CreateQuestion{
 4     public static String getQuestion(){       //得到一个String类型的问题
 5         CreateNumber getnum = new CreateNumber();   //新建一个CreateNumber类的对象getnum
 6         CreateOperator getope = new CreateOperator();     //新建一个CreateOperator类的对象getope
 7         Random r1 = new Random();
 8         String s = new String();
 9         int i,a1,a2,a3;
10         a1 = r1.nextInt(4);  //随机生成一个[0,4)的数a1,这决定了公式的长度
11         String question,qt;
12         question=getnum.getNumber() + getope.getOperator();   //初始化变量question
13         for(int j=1;j<=a1;j++){
14             qt=getnum.getNumber()+getope.getOperator();
15             question=question+qt;    //循环增加问题长度
16         }
17         question= question+getnum.getNumber();   //给问题的最后加上一个数
18         char[] q = question.toCharArray();     //将生成的字符串变成字符数组,以便在里边加括号
19         if(q.length>9){           // 字符串的长度在9以上才会进行加括号操作,避免出现因找不到加括号的位置而出现死循环的情况
20             for(i=0;;i++){              //通过循环找寻适合加左括号的位置
21                 a2=r1.nextInt(q.length-3);     //随机生成一个位置的下标,在排除最后3个字符的字符串里找位置生成左括号,以便留给右括号空间
22                 if(a2<=5){
23                     if(q[a2]=='+' || q[a2]=='-' || q[a2]=='*' || q[a2]=='÷'){
24                         break;       //若找到合适位置便退出循环
25                     }
26                     else continue;    //未找到则继续循环
27                 }
28             }
29             char[] z1=addElementToArray1(q,a2);  //调用方法,把字符数组q[],位置a2传到方法里边
30             for(i=0;;i++){           //通过循环找寻适合加右括号的位置
31                 a3=r1.nextInt(z1.length);    //随机生成一个位置的下标,找位置生成右括号
32                 if(a3>a2+4){     //右括号的位置应该比左括号的位置靠右至少4位
33                     if( z1[a3]=='+' || z1[a3]=='-' || z1[a3]=='*' || z1[a3]=='÷' || a3==q.length){
34                         break;     //若找到合适位置便退出循环
35                     }
36                     else continue;     //未找到则继续循环
37                 }
38             }
39             char[] z2=addElementToArray2(q,z1,a3);    //调用方法,把字符数组q[],z1[],位置a2传到方法里边
40             s = new String(z2);
41             return s;
42         }
43         else s = new String(q);
44         return s;
45 
46     }
47 
48 
49 
50     public static char[] addElementToArray1(char[] q,int a2) {
51         char [] z1 = new char[q.length+1];     //字符数组z1长度相比q加一
52         int i;
53         for(i=z1.length-1;i>a2;i--){
54             z1[i]=q[i-1];
55         }
56         z1[a2+1]='(';
57         for(i=0;i<=a2;i++){
58             z1[i]=q[i];
59         }     //在q[]中a2位置加入左括号,写入z1[]
60 
61         return z1;
62     }
63 
64     public static char[] addElementToArray2(char[] q,char[] z1,int a3) {
65         char [] z2 = new char[z1.length+1];   //字符数组z2长度相比z1加一
66         int i;
67         if(z1[a3]=='+' || z1[a3]=='-' || z1[a3]=='*' || z1[a3]=='÷'){
68             for(i=z2.length-1;i>a3;i--){
69                 z2[i]=z1[i-1];
70             }
71             z2[a3]=')';
72             for(i=0;i<=a3-1;i++){
73                 z2[i]=z1[i];
74             }
75         }  //若a3位置为一个运算符,在z1[]中a3位置加入左括号,写入z2[]
76         else {
77             for(i=0;i<z1.length;i++){
78                 z2[i]=z1[i];
79             }
80             z2[z1.length]=')';
81         }   //若a3等于字符数组q的长度,将z1写入z2,并在z2末尾加上右括号
82         return z2;
83     }
84 }

将生成的问题转为后缀表达式代码:

 1 import java.util.*;
 2 public class ChangeSuffix {
 3     String questions;
 4     String after_questions= "";
 5     public String toSuffix(String questions){
 6         this.questions=questions;
 7         Stack stack=new Stack();     //新建一个栈
 8         for (int i=0;i<questions.length();i++) {
 9             char ele=questions.charAt(i);   //取出字符串的第i个字符
10             if (ele>='0'&&ele<='9'){
11                 after_questions=after_questions+ele;   //如果第i个字符为数字,直接写入到后缀表达式中
12             }
13             else if (ele=='+'||ele=='-'||ele=='*'||ele=='÷'||ele=='/') {
14                 after_questions=after_questions+" ";       //如果遇到运算符
15                 if (stack.empty()){
16                     stack.push(ele);
17                 }      //若栈为空,字符进栈
18                 else if (priority(ele)>priority((char)stack.peek())) {
19                     stack.push(ele);
20                 }       //如果栈顶元素的运算优先级小于当前字符的运算优先级,当前元素进栈
21                 else{
22                     after_questions=after_questions+ String.valueOf(stack.pop())+" ";
23                     i--;
24                 }     //其他情况即如果栈顶元素的运算优先级大于或等于当前字符的运算优先级,将该元素出栈,变成字符串类型
25             }
26             else if(ele=='('){
27                 stack.push(ele);
28             }     //如果当前元素为左括号,进栈
29             else if(ele==')'){
30                 after_questions+=" ";
31                 while((char)stack.peek()!='('){
32                     after_questions=after_questions+ String.valueOf(stack.pop())+" ";
33                 }    //如果栈顶元素不是左括号,连续将栈顶元素弹出变成字符串类型写入到后缀表达式中
34                 stack.pop();  //将左括号弹出
35             }
36         }
37         after_questions+=" ";
38         while(!stack.empty()){
39             after_questions=after_questions+String.valueOf(stack.pop())+" ";
40         }    //如果栈顶元素非空,连续弹出栈顶元素变成字符串类型写入到后缀表达式中
41         return after_questions;
42     }
43     public int priority (char c){       //定义运算符的优先级
44         int pt =0;
45         switch(c){
46             case '(':
47                 pt=1;
48                 break;
49             case '+':
50             case '-':
51                 pt=2;
52                 break;
53             case '*':
54             case '÷':
55                 pt=3;
56                 break;
57             case '/':
58                 pt=4;
59                 break;
60             case ')':
61                 pt=5;
62                 break;
63             default:
64                 pt=0;
65                 break;
66         }
67         return pt;
68     }
69 }

计算后缀表达式代码:

 1 import java.util.*;
 2 public class Calculation {
 3     /** constant for addition symbol */
 4     private final char ADD = '+';
 5     /** constant for subtraction symbol */
 6     private final char SUBTRACT = '-';
 7     /** constant for multiplication symbol */
 8     private final char MULTIPLY = '*';
 9     /** constant for division symbol */
10     private final char DIVIDE = '÷';
11     private final char SEMICON= '/';
12     /** the stack */
13     private Stack<String > stack;
14 
15     public Calculation() {
16         stack = new Stack<String>();    //创建一个String类型的栈
17     }
18 
19     public String evaluate (String expr)
20     {
21         String op1,op2;
22         Fraction p1;
23         Fraction p2;
24         String token;
25         String result ="";
26         StringTokenizer tokenizer = new StringTokenizer (expr);  //将expr后缀表达式传进StringTokenizer方法
27 
28         while (tokenizer.hasMoreTokens())
29         {
30             token = tokenizer.nextToken();    //将StringTokenizer分解出的字符串赋给token
31 
32             //如果是运算符,调用isOperator
33             if (isOperator(token))
34             {
35                 //从栈中弹出操作数2
36                 op2=stack.pop();
37                 p2 = toFraction(op2);
38                 //从栈中弹出操作数1
39                 op1=stack.pop();
40                 p1 =toFraction(op1);
41                 //根据运算符和两个操作数调用evalSingleOp计算result;
42                 result=evalSingleOp(token.toCharArray()[0],p1,p2);
43                 //计算result入栈;
44                 stack.push(result);
45             }
46             else//如果是操作数,入栈
47                 stack.push(token);
48         }
49 
50         return result;
51     }
52 
53     private boolean isOperator (String token)
54     {
55         return ( token.equals("+") || token.equals("-") ||
56                 token.equals("*") || token.equals("÷")||token.equals("/") );    //若token是运算符,返回true
57     }
58 
59     private String evalSingleOp (char operation, Fraction p1, Fraction p2)
60     {
61         Fraction result = new Fraction(0,1);     //初始化result
62 
63         switch (operation)    //根据运算符选择运算
64         {
65             case ADD:
66                 result =p1.add(p2);
67                 break;
68             case SUBTRACT:
69                 result = p1.minus(p2);
70                 break;
71             case MULTIPLY:
72                 result = p1.multiply(p2);
73                 break;
74             case DIVIDE:
75             case SEMICON:
76                 result = p1.divide(p2);
77         }
78 
79         return result.toString();     //将结果变成字符串
80     }
81     private Fraction toFraction(String m){
82         String token1, token2;
83         Fraction r = new Fraction(0,1);
84         StringTokenizer tokenizer1 = new StringTokenizer(m, "/");//把操作数以"/"为标记分割开来
85         token1 = tokenizer1.nextToken();
86         if (tokenizer1.hasMoreTokens()) {//如果有第二个元素就把token1放在分子的位置,token2放在分母的位置
87             token2 = tokenizer1.nextToken();
88             int toke1 =Integer.valueOf(token1).intValue();
89             int toke2 =Integer.valueOf(token2).intValue();
90             r =new Fraction(toke1,toke2);
91         }
92         else {//如果没有第二个元素就把token1放在分子的位置,分母固定为1
93             int toke1 = Integer.valueOf(token1).intValue();
94             r =new Fraction(toke1,1);
95         }
96         return r;
97 
98     }
99 }

 

四、测试方法

1.Fraction类分数类的测试:

2.ChangeSuffix类将问题转为后缀表达式类的测试

3.Calculation类计算类的测试

五、运行结果截图

六、代码托管

七、遇到的问题及解决办法

问题1:分数有一个很重要的特点,就是他的分母不可以为0。编写分数时,不知道如果生成了这种情况该怎么处理,就强行将分母为0的时候给分母强行改成其他值,虽然写法在理论上应该是没毛病的,可是他就是运行不出结果,程序就不往下运行了。

问题1解决办法:网上搜可以用ArithmeticException方法将此问题抛出并退出程序,就不会出现程序既不退出也无法继续运行的情况了。现在想想,可以将随机生成的分母加1,这样就不会出现分母为0的情况了。

问题2:在生成的无括号的题目里面加括号,如果生成的算式很短,且第一个括号加的靠后,那么第二个括号可能会找不到位置,或者括号只能括起来一个数。

问题2解决办法:如果算式特别短,将不生成括号,只有在字符串长度大于9才会进行生成括号的操作,这样虽然不能给一些数小的但能加括号算式加括号,但问题能避免了。

问题3:本来生成的无括号的题目是一个字符串,在里面我能找元素,但是我不会在里面加元素。

问题3解决办法:网上说,可以用toCharArray方法把字符串化成字符数组,然后进行操作,操作完以后再把它转化成字符串。

问题4:对后缀表达式进行计算时,看了栈的基本操作,仿照老师的后缀表达式计算代码将分数的计算也加进去。使用过程中发现Stack中的E为栈储存的数据形式,如果数据形式不一致将无法储存

问题4解决办法:将栈改为存储string类型。还有一个改动是要将其中的“/”和“÷”进行区分,“/”专指分号,优先度要高于乘除符号,但算法可以与除号等同。

八、对结对的小伙伴做出评价

这两天确实是非常感谢他的付出、陪伴与理解。我们这个结对项目,按老师的要求就是一个驾驶员,一个领航员。这确实是一种非常好的合作方式,可是,在做Java的同时,要学其他科目,要做好自己的学生工作等等,这导致我们并没有太多时间在一起写同一个代码,更多的是我们分好工,你把你的做好,我把我的做好,如果有问题一起想想,两个人一起解决一下。也许是我们对老师所说的一个驾驶员一个领航员的合作方式还不适应,所以不能在以其高效率的写代码,以后会继续向这方面努力。我们合作期间,我可能要做的其他的事多一下,做事也磨蹭一点,将代码写出来总是慢他三分,所以他付出的会更多一些,感谢他的付出;因为我做的慢,他偶尔也会埋怨我一下,但过两分钟,就又来给我加油了,真也非常感谢他的陪伴与理解。下次我会尽量的加快我的脚步,做事效率高一点,不能让你做那么多,我也要为咱们俩的项目付出更多!

九、PSP图:

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划    
·Estimate · 估计这个任务需要多少时间 35 45
Development 开发    
·Anlaysis · 需求分析 (包括学习新技术) 50 100
·Design Spec · 生成设计文档 30 40
·Design Review · 设计复审 (和同事审核设计文档) 40 25
·Coding Standard · 代码规范 (为目前的开发制定合适的规范) 45 45
·Design · 具体设计 60 140
·Coding · 具体编码 120 160
· Code Review · 代码复审 60 40
· Test · 测试(自我测试,修改代码,提交修改) 140 170
Reporting 报告    
· Test Report · 测试报告 60 90
· Size Measurement · 计算工作量 5 15
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 45
  合计 675 915
原文地址:https://www.cnblogs.com/cxd20175303/p/10657402.html