第二次作业

1.项目Github地址

https://github.com/Vertigor/FourOperation

2.题目

(四则运算题目生成程序(基于控制台)){https://edu.cnblogs.com/campus/whu/2017ASE/homework/952}

3.估计花费时间

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划  10  
· Estimate · 估计这个任务需要多少时间  10  
Development 开发  360  
· Analysis · 需求分析 (包括学习新技术)  30  
· Design Spec · 生成设计文档  20  
· Design Review · 设计复审 (和同事审核设计文档)  10  
· Coding Standard · 代码规范 (为目前的开发制定合适的规范)  10  
· Design · 具体设计  30  
· Coding · 具体编码  230  
· Code Review · 代码复审  20  
· Test · 测试(自我测试,修改代码,提交修改)  10  
Reporting 报告  60  
· Test Report · 测试报告  30  
· Size Measurement · 计算工作量  10  
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划  20  
合计    430  

3.

4.解题思路

  在拿到题目后,一开始想的是算法的实现,后来感觉算法是一定可以实现的,然而运行在什么样的环境,是至关重要的。本来想选用最近一直在用的JAVA,但是由于我不会JAVA的界面编程,最后还是选择了之前比较熟练的C#。又想到这个项目应该有很好的兼容性,网页似乎就是一个不错的选择。但后来又发现题目要求基于控制台,感觉自己白浪费这么多时间在界面上了。汗。。。最后还是用JAVA吧,电脑上的环境已经搭好了,不用重新搭C#的环境了。

  Java是一门面向对象的语言,所以分析题目中的对象是很重要的。首先,要把界面、功能模块、控制分割开,采用MVC模式设计。其次,用户操作的对象是题目,将题目作为对象生成一个类(Question)。题目中要求可以进行真分数的运算,我的想法是把每个数字都当成是真分数来运算,整数相当于分母为1,所以我又生成一个类(Fractions)。光有对象没有操作不行的,我又声明一个类(Calculate),用来计算真分数的加减乘除。最后,生成一个(Control)类,用来处理与用户交互的图形界面,这样程序的框架就搭好了。

  完成框架后,具体设计还需满足以下需求:

  1. 操作数必须随机生成。
  2. 运算符的种类和顺序必须随机生成。
  3. 可以判断用户输入的对错。
  4. 使用-n参数控制生成题目的个数。
  5. 支持带括号的多元复合运算。
  6. 运算符个数随机生成。

  补充:考虑真分数可以约分成整数,所以Fractions中应该包含changeToInteger()函数,将结果保存成整数。括号的数目和位置也应该随机生成,满足数学约束,并且能够嵌套。

5.设计实现过程

 总流程图

子流程图

  Fractions用两个int变量分别表示分子分母,提供静态函数maxCommonDivisor(int,int)和minCommonMultiple(int, int),分别是求最大公约数函数和最小公倍数函数,还包含将可转化为整数的分数转化为整数的函数changeToInteger()。

  Question采用两种数组保存操作数,分别是分数操作数和整数操作数,又创建两个括号数组,分别是左括号和右括号,专门的乘除运算符数组以及用于计算的两个堆栈。包括检查括号约束情况函数checkBracket()、计算函数calculate()、优先级比较函数compare(str: char)等。

  Calculate包含四个静态函数,分别是加减乘除。Control包含main函数,从控制台读取到题目个数,与用户进行交互。

6.代码说明

代码说明已存在注释中。

6.1Question类如下:

  1 package question;
  2 
  3 import java.util.Random;
  4 import java.util.Stack;
  5 
  6 import fraction.Fractions;
  7 import calculate.Calculate;
  8 
  9 public class Question {
 10     private Character[] operators;//操作符数组
 11     private int[] operands;//操作数数组
 12     private Fractions[] operands_fra;//操作数分数数组
 13     private int operators_num;//运算符数目
 14     private Fractions result;//计算结果
 15     private Stack<Character> priStack;// 操作符栈   
 16     private Stack<Fractions> numStack;// 操作数栈
 17     private int[] leftBracket;//左括号
 18     private int[] rightBracket;//右括号
 19     private int bracketNum;//括号数量
 20     private String expression;//表达式字符串
 21     public Question(int operators_num){
 22         if(operators_num<1||operators_num>10){
 23             System.out.println("Error:operators number error!");
 24             return;
 25         }
 26         this.operators_num = operators_num;
 27         this.operands = new int[operators_num+1];
 28         this.operators = new Character[operators_num];
 29         this.operands_fra = new Fractions[operators_num+1];
 30         this.init();
 31     }
 32     //初始化各种参数
 33     private void init(){
 34         Random random=new Random();
 35         if(operators_num==1)
 36             bracketNum=0;
 37         else
 38             bracketNum=random.nextInt(operators_num/2+operators_num%2+1);
 39         leftBracket = new int[operators_num];
 40         rightBracket = new int[operators_num];
 41         priStack = new Stack<Character>();
 42         numStack = new Stack<Fractions>();
 43         initBracketArray();
 44         if(bracketNum>0){
 45         for(int i=0;i<this.bracketNum;i++){
 46             int pos = random.nextInt(operators_num);
 47             leftBracket[pos]++;
 48             rightBracket[random.nextInt(operators_num-pos)+pos]++;
 49 
 50         }
 51         checkBracket();
 52         }
 53         for(int i=0;i<this.operands.length;i++){
 54             operands[i]=random.nextInt(100)+1;
 55         }
 56         for(int i=0;i<this.operands_fra.length;i++){
 57             operands_fra[i]=new Fractions(operands[i],1);
 58         }
 59         for(int i=0;i<this.operators.length;i++){
 60             switch(random.nextInt(4)){
 61             case 0:
 62                 operators[i]='+';
 63                 break;
 64             case 1:
 65                 operators[i]='-';
 66                 break;
 67             case 2:
 68                 operators[i]='*';
 69                 break;
 70             case 3:
 71                 operators[i]='/';
 72                 break;
 73             }
 74         }
 75         this.setExpression(printQuestion());
 76         this.calculate();
 77     }
 78     //初始化括号数组
 79     private void initBracketArray(){
 80         for(int i=0;i<this.operators_num;i++){
 81             leftBracket[i]=0;
 82             rightBracket[i]=0;
 83         }
 84     }
 85     //检查括号是否满足约束,不满足删除括号
 86     private boolean checkBracket(){
 87         boolean flag = true;
 88         int[] lb = leftBracket.clone();
 89         int[] rb = rightBracket.clone();
 90         for(int i=0;i<operators_num;i++){
 91             int temp =i;
 92             while(rb[i]>0){
 93                 for(int j=i;j>-1;j--){
 94                     while(lb[j]>0&&rb[i]>0){
 95                         lb[j]--;
 96                         rb[i]--;
 97                         if(temp-1==j||temp==j||(i==operators_num-1&&j==0)){
 98                             deleteBracket(j, i);
 99                             flag = false;
100                         }
101                         temp=j;
102                     }
103                 }
104             }
105         }
106         return flag;
107     }
108     //删除括号
109     private boolean deleteBracket(int lb,int rb){
110         if(leftBracket[lb]==0||rightBracket[rb]==0)
111             return false;
112         leftBracket[lb]--;
113         rightBracket[rb]--;
114         bracketNum--;
115         return true;
116     }
117     //打印表达式字符串
118     private String printQuestion(){
119         String str="";
120         for(int i=0;i<operators_num;i++){
121             for(int j=0;j<leftBracket[i];j++){
122                 str+="(";
123             }
124             str+=operands[i];
125             if(i>0){
126                 for(int j=0;j<rightBracket[i-1];j++){
127                     str+=")";
128                 }
129             }
130             str+=operators[i].toString();
131         }
132         str+=operands[operators_num];
133         if(bracketNum>0)
134         for(int j=0;j<rightBracket[operators_num-1];j++){
135             str+=")";
136         }
137         str+="=";
138         return str;
139     }
140     //计算表达式
141     private void calculate(){
142         numStack.push(operands_fra[0]);
143         int i=0;
144         int[] lb = leftBracket.clone();
145         int[] rb = rightBracket.clone();
146         while(i<operators_num){
147             while(lb[i]>0){
148                 priStack.push('(');
149                 lb[i]--;
150             }
151             if(i>0){
152                 if(rb[i-1]>0){
153                     char ope = priStack.pop();
154                     if(ope=='(')
155                         continue;
156                     Fractions b = (Fractions) numStack.pop();// 第二个运算数
157                     Fractions a = (Fractions) numStack.pop();// 第二个运算数
158                     Fractions tempresult ;
159                     switch (ope) {
160                     // 如果是加号或者减号,则   
161                     case '+':
162                         tempresult = Calculate.addtion(a, b);
163                         numStack.push(tempresult);
164                         break;
165                     case '-':
166                         tempresult = Calculate.subtraction(a, b);
167                         numStack.push(tempresult);
168                         break;
169                     case '*':
170                         tempresult = Calculate.multiplication(a, b);
171                         numStack.push(tempresult);
172                         break;
173                     case '/':
174                         tempresult = Calculate.division(a, b);
175                         numStack.push(tempresult);
176                         break;
177                     }
178                     rb[i-1]--;
179                 }
180             }
181             if(!compare(operators[i])){
182                 Fractions b = (Fractions) numStack.pop();// 第二个运算数
183                 Fractions a = (Fractions) numStack.pop();// 第二个运算数
184                 char ope = priStack.pop();
185                 Fractions tempresult ;
186                 switch (ope) {
187                 // 如果是加号或者减号,则   
188                 case '+':
189                     tempresult = Calculate.addtion(a, b);
190                     numStack.push(tempresult);
191                     break;
192                 case '-':
193                     tempresult = Calculate.subtraction(a, b);
194                     numStack.push(tempresult);
195                     break;
196                 case '*':
197                     tempresult = Calculate.multiplication(a, b);
198                     numStack.push(tempresult);
199                     break;
200                 case '/':
201                     tempresult = Calculate.division(a, b);
202                     numStack.push(tempresult);
203                     break;
204                 }
205             }else{
206                 priStack.push(operators[i]);
207                 numStack.push(operands_fra[i+1]);
208                 i++;
209             }
210         }
211         while(!priStack.isEmpty()){
212             char ope = priStack.pop();
213             if(ope=='(')
214                 continue;
215             Fractions b = (Fractions) numStack.pop();// 第二个运算数
216             Fractions a = (Fractions) numStack.pop();// 第一个运算数
217             Fractions tempresult ;
218             switch (ope) {
219             // 如果是加号或者减号,则   
220             case '+':
221                 tempresult = Calculate.addtion(a, b);
222                 numStack.push(tempresult);
223                 break;
224             case '-':
225                 tempresult = Calculate.subtraction(a, b);
226                 numStack.push(tempresult);
227                 break;
228             case '*':
229                 tempresult = Calculate.multiplication(a, b);
230                 numStack.push(tempresult);
231                 break;
232             case '/':
233                 tempresult = Calculate.division(a, b);
234                 numStack.push(tempresult);
235                 break;
236             }
237         }
238 
239         result = numStack.pop();
240     }
241     private boolean compare(char str) {   
242         if (priStack.empty()) {   
243           // 当为空时,显然 当前优先级最低,返回高   
244           return true;   
245         }   
246         char last = (char) priStack.lastElement();   
247         // 如果栈顶为'('显然,优先级最低,')'不可能为栈顶。   
248         if (last == '(') {   
249           return true;   
250         }   
251         switch (str) {   
252         case '=':   
253           return false;// 结束符   
254         case '(':   
255           // '('优先级最高,显然返回true   
256           return true;   
257         case ')':   
258           // ')'优先级最低,   
259           return false;   
260         case '*': {   
261           // '*/'优先级只比'+-'高   
262           if (last == '+' || last == '-')   
263             return true;   
264           else  
265             return false;   
266         }   
267         case '/': {   
268           if (last == '+' || last == '-')   
269             return true;   
270           else  
271             return false;   
272         }   
273           // '+-'为最低,一直返回false   
274         case '+':   
275           return false;   
276         case '-':   
277           return false;   
278         }   
279         return true;   
280       }
281     public Fractions getResult() {
282         return result;
283     }
284     public String getExpression() {
285         return expression;
286     }
287     private void setExpression(String expression) {
288         this.expression = expression;
289     }  
290 
291 }
Question

 

6.2Calculate类如下:

 1 package calculate;
 2 
 3 import fraction.Fractions;
 4 public  class Calculate {
 5    
 6     public Calculate(){
 7     }
 8     // 加法计算
 9     public static Fractions addtion(Fractions fractions1,Fractions fractions2)
10     {
11         int result_numerator,min;  // 相加后的分子以及两分数分母的最小公倍数
12         min=Fractions.minCommonMultiple(fractions1.getDenominator(), fractions2.getDenominator());
13         result_numerator=(min/fractions1.getDenominator())*fractions1.getNumerator()+(min/fractions2.getDenominator())*fractions2.getNumerator();
14         Fractions result=new Fractions(result_numerator, min);
15         return result;
16     }
17     // 减法计算
18     public static Fractions subtraction(Fractions fractions1,Fractions fractions2)
19     {
20         int result_numerator,min;  // 相减后的分子以及两分数分母的最小公倍数
21         min=Fractions.minCommonMultiple(fractions1.getDenominator(), fractions2.getDenominator());
22         result_numerator=(min/fractions1.getDenominator())*fractions1.getNumerator()-(min/fractions2.getDenominator())*fractions2.getNumerator();
23         Fractions result=new Fractions(result_numerator, min);
24         return result;
25     }
26     // 乘法计算
27     public static Fractions multiplication(Fractions fractions1,Fractions fractions2)
28     {
29         int result_numerator,result_denominator;  // 相乘后的分子和分母
30         result_numerator=fractions1.getNumerator()*fractions2.getNumerator();
31         result_denominator=fractions1.getDenominator()*fractions2.getDenominator();
32         Fractions result=new Fractions(result_numerator, result_denominator);
33         return result;
34     }
35     // 除法计算
36     public static Fractions division(Fractions fractions1,Fractions fractions2)
37     {
38         int result_numerator,result_denominator;  // 相除后的分子和分母
39         // 分数相除问题转换成分数相乘问题
40         result_numerator=fractions1.getNumerator()*fractions2.getDenominator();
41         result_denominator=fractions1.getDenominator()*fractions2.getNumerator();
42         Fractions result=new Fractions(result_numerator, result_denominator);
43         return result;
44     }
45 }
Calculate

 

6.3Control类如下:

package control;

import java.util.Random;
import java.util.Scanner;

import fraction.Fractions;
import question.Question;

public class Control {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("本次共有"+args[1]+"道题。");
        if(args[0].equals("-n")){
            Scanner scanner=new Scanner(System.in);
            Integer num=new Integer(args[1]);
            int correct=0;
            String answer[]=new String[num];//用户输入的答案
            Random random = new Random();
            boolean judge[] = new boolean[num];//保存用户输入对错
            Question[] questions = new Question[num];
            for(int i=0;i<num;i++){
                questions[i] = new Question(random.nextInt(10)+1);
                System.out.print((i+1)+":"+questions[i].getExpression());
                answer[i] = scanner.nextLine();
                Fractions result = questions[i].getResult();
                int result_int = result.changeToInteger();
                if(!answer[i].equals("")){
                if(result_int!=Integer.MAX_VALUE){
                    if(Integer.parseInt(answer[i])==result_int){
                        judge[i]=true;
                        System.out.println("正确!");
                        correct++;
                    }else{
                        judge[i]=false;
                        System.out.println("不正确!正确答案:"+result_int);
                    }
                }else{
                    String splits[] = answer[i].split("/");
                    if(splits.length==2&&Integer.parseInt(splits[0])==result.getNumerator()&&Integer.parseInt(splits[1])==result.getDenominator()){
                        judge[i]=true;
                        System.out.println("正确!");
                        correct++;
                    }else{
                        judge[i]=false;
                        System.out.println("不正确!正确答案:"+result.printFraction());
                    }
                }
                }else{
                    judge[i]=false;
                    System.out.println("未回答!正确答案:"+result.printFraction());
                }
            }
            double score = (double)correct/(double)num*100.00;
            System.out.println("本次得分:"+score);
            scanner.close();
        }else{
            System.out.println("命令有误");
        }

    }

}
Control

 

6.4Fractions类如下:

 1 package fraction;
 2 
 3 public class Fractions {
 4     private int numerator;  //分子
 5     private int denominator;  //分母
 6     // 无参数构造器
 7     public Fractions(){
 8     }
 9     //参数构造器
10     public Fractions(int numerator,int denominator){
11         this.setValue(numerator, denominator);
12     }
13     // 设置分子分母
14     public void setValue(int numerator,int denominator)
15     {
16         if(numerator==0){
17             this.numerator=0;
18             this.denominator=1;
19             return;
20         }
21         if(denominator==0){
22             System.out.println("Error:denominator equals zero!");
23         }
24         int temp=maxCommonDivisor(denominator, numerator);  //temp为最大公约数
25         this.numerator=numerator/temp;
26         this.denominator=denominator/temp;
27     }
28     // 求最大公约数
29     public static int maxCommonDivisor(int d, int n) 
30     {  
31         if (d < n) {// 保证d>n,若d<n,则进行数据交换  
32             int temp = d;  
33             d = n;  
34             n = temp;  
35         }  
36         while (d % n != 0) {// 在余数不能为0时,进行循环  
37             int temp = d % n;  
38             d = n;  
39             n = temp;  
40         }  
41         return n;// 返回最大公约数  
42     }
43     // 求最小公倍数
44     public static int minCommonMultiple(int m, int n) {  
45         return m * n / maxCommonDivisor(m, n);  
46     }  
47     // 打印分数
48     public String printFraction()
49     {
50         return (this.numerator+"/"+this.denominator).toString();
51     }
52     // 获取分子
53     public int getNumerator()
54     {
55         return this.numerator;
56     }
57     // 获取分母
58     public int getDenominator()
59     {
60         return this.denominator;
61     }
62     //判断是否可以转化为整数
63     private boolean isInteger(){
64         if(this.denominator==1||this.denominator==-1)
65             return true;
66         else return false;
67     }
68     //转换为整数
69     public int changeToInteger(){
70         if(this.isInteger())
71             return this.getNumerator();
72         else 
73             return Integer.MAX_VALUE;
74     }
75 }
Fractions

 

7.测试运行

7.1程序测试

  程序截图如下:

  由上图可知,程序界面显示没问题,满足需求,括号也满足了嵌套,数学约束,结果由人工手算也是正确的。当然,这张图只是程序跑一次的结果,这个程序我跑了50次左右,结果都是正确(其中出BUG的部分也被修补过了)。在运行中,一开始结果是不正确的,错误集中在calculate()函数中,栈的入栈出栈顺序有问题,编程的时候没发现,发现错误的时候很难找出来。在括号数学约束中,bug也是很多的,会出现(34)+23这样的表达式,这段代码我看了很多遍,一直都没发现错误,后来才发现是少减了1,(⊙﹏⊙)b这个bug我纠结了2个小时。

7.2单元测试

7.2.1Calculate功能测试

  先展示单元测试的结果图:

  在此次单元测试中,分别测试了Calculate类中加减乘除的运算,例如:测试加法函数,输入(1/2+1/2),对比结果是否为1。具体测试类如下:

 1 package calculate;
 2 
 3 import static org.junit.Assert.*;
 4 
 5 import org.junit.Before;
 6 import org.junit.Test;
 7 
 8 import fraction.Fractions;
 9 
10 public class CalculateTest {
11 
12     @Before
13     public void setUp() throws Exception {
14     }
15 
16     @Test
17     public void testAddtion() {
18         Fractions result = Calculate.addtion(new Fractions(1,2), new Fractions(1,2));
19         assertEquals(1, result.getNumerator());
20         assertEquals(1, result.getDenominator());
21     }
22 
23     @Test
24     public void testSubtraction() {
25         Fractions result = Calculate.subtraction(new Fractions(1,2), new Fractions(1,2));
26         assertEquals(0, result.getNumerator());
27         assertEquals(1, result.getDenominator());
28     }
29 
30     @Test
31     public void testMultiplication() {
32         Fractions result = Calculate.multiplication(new Fractions(1,2), new Fractions(1,2));
33         assertEquals(1, result.getNumerator());
34         assertEquals(4, result.getDenominator());
35     }
36 
37     @Test
38     public void testDivision() {
39         Fractions result = Calculate.division(new Fractions(1,2), new Fractions(1,2));
40         assertEquals(1, result.getNumerator());
41         assertEquals(1, result.getDenominator());
42     }
43 
44 }

 

7.2.2Fractions功能测试

  Fractions类的测试结果如图:

  在Fractions中主要测试的是最大公约数,最小公倍数函数,分数转整数,打印分数,设置分数值。代码如下:

 1 package fraction;
 2 
 3 import static org.junit.Assert.*;
 4 
 5 import org.junit.Before;
 6 import org.junit.Test;
 7 
 8 public class FractionsTest {
 9     
10     private  Fractions fraction;
11 
12     @Before
13     public void setUp() throws Exception {
14         fraction = new Fractions(1,1);
15     }
16 
17     @Test
18     public void testSetValue() {
19         fraction.setValue(2, 3);
20         assertEquals(2, fraction.getNumerator());
21         assertEquals(3, fraction.getDenominator());
22     }
23 
24     @Test
25     public void testMaxCommonDivisor() {
26         int divisor = fraction.maxCommonDivisor(9, 6);
27         assertEquals(3, divisor);
28     }
29 
30     @Test
31     public void testMinCommonMultiple() {
32         int multiple = fraction.minCommonMultiple(9, 6);
33         assertEquals(18, multiple);
34     }
35 
36     @Test
37     public void testPrintFraction() {
38         assertEquals("1/1", fraction.printFraction());
39     }
40 
41     @Test
42     public void testChangeToInteger() {
43         assertEquals(1, fraction.changeToInteger());
44     }
45 
46 }

 7.3代码覆盖测试

  代码覆盖测试使用的是EclEmma插件,运行结果截图如下:

  程序覆盖率在82%,其中有一些是判断失败的语句以及一些表达式并没有生成括号,所以一些代码没有运行到。

8.实际花费的时间

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划  10  30
· Estimate · 估计这个任务需要多少时间  10  30
Development 开发  360  600 
· Analysis · 需求分析 (包括学习新技术)  30  30
· Design Spec · 生成设计文档  20  60
· Design Review · 设计复审 (和同事审核设计文档)  10  10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范)  10  10
· Design · 具体设计  30  30
· Coding · 具体编码  230  420
· Code Review · 代码复审  20  10
· Test · 测试(自我测试,修改代码,提交修改)  10  30
Reporting 报告  60  360
· Test Report · 测试报告  30  330
· Size Measurement · 计算工作量  10  10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划  20  20
合计    430  990

9.项目小结

  此次项目帮助我深入了解了PSP的工作流程,从计划、开发到测试,每一步都亲身体验,从中学到了许多,掌握了很多Eclipse的插件,比如:Eclipse自带的Git插件、GEF插件、单元测试等。我深刻体会到软件工程的系统性和复杂性,正如:

软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。它涉及程序设计语言、数据库、软件开发工具、系统平台、标准、设计模式等方面。
在现代社会中,软件应用于多个方面。典型的软件有电子邮件、嵌入式系统、人机界面、办公套件、操作系统、编译器、数据库、游戏等。同时,各个行业几乎都有计算机软件的应用,如工业、农业、银行、航空、政府部门等。这些应用促进了经济和社会的发展,也提高了工作效率和生活效率 。

  由于一开始没有单元测试的概念,导致单元测试都是在完成整个软件的时候才开始的。单元测试应该在完成每一个功能模块的时候就进行,这样才能保证每一个功能的正确性。并且在更改和更新的时候还需要做回归测试,保证原有功能正常,不受新功能的影响。

posted @ 2017-09-26 15:54  黄雨橙子  阅读(230)  评论(2编辑  收藏  举报