天天看點

第二次作業

1.項目Github位址

https://github.com/Vertigor/FourOperation

2.題目

(四則運算題目生成程式(基于控制台)){https://edu.cnblogs.com/campus/whu/2017ASE/homework/952}

3.估計花費時間

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃  10
· Estimate · 估計這個任務需要多少時間
Development 開發  360
· Analysis · 需求分析 (包括學習新技術)  30
· Design Spec · 生成設計文檔  20
· Design Review · 設計複審 (和同僚稽核設計文檔)
· Coding Standard · 代碼規範 (為目前的開發制定合适的規範)
· Design · 具體設計
· Coding · 具體編碼  230
· Code Review · 代碼複審
· Test · 測試(自我測試,修改代碼,送出修改)
Reporting 報告  60
· Test Report · 測試報告
· Size Measurement · 計算工作量
· Postmortem & Process Improvement Plan · 事後總結, 并提出過程改進計劃
合計  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.實際花費的時間

 600 
 420
 330
 990

9.項目小結

  此次項目幫助我深入了解了PSP的工作流程,從計劃、開發到測試,每一步都親身體驗,從中學到了許多,掌握了很多Eclipse的插件,比如:Eclipse自帶的Git插件、GEF插件、單元測試等。我深刻體會到軟體工程的系統性和複雜性,正如:

軟體工程是一門研究用工程化方法建構和維護有效的、實用的和高品質的軟體的學科。它涉及程式設計語言、資料庫、軟體開發工具、系統平台、标準、設計模式等方面。

在現代社會中,軟體應用于多個方面。典型的軟體有電子郵件、嵌入式系統、人機界面、辦公套件、作業系統、編譯器、資料庫、遊戲等。同時,各個行業幾乎都有計算機軟體的應用,如工業、農業、銀行、航空、政府部門等。這些應用促進了經濟和社會的發展,也提高了工作效率和生活效率 。

  由于一開始沒有單元測試的概念,導緻單元測試都是在完成整個軟體的時候才開始的。單元測試應該在完成每一個功能子產品的時候就進行,這樣才能保證每一個功能的正确性。并且在更改和更新的時候還需要做回歸測試,保證原有功能正常,不受新功能的影響。