2016012032小學四則運算練習軟件項目報告


Coding.net源碼倉庫地址:https://git.coding.net/wanghz499/2016012032week2-2.git

測試步驟:

1.進入src文件夾

2.在命令行輸入javac -encoding utf-8 Main.java

3.回車再輸入java Main 20

4.回車,將會在根目錄下(與src同級)產生result.txt

一、需求分析

    通過對題目要求的分析,我共提取出以下7個需求(實現帶括號和真分數的附加功能):

    1.程序可從命令行接收一個輸入參數n,然后隨機產生n道加減乘除練習題。

    2.每個數字在 0 和 100 之間,運算符在3個到5個之間。

    3.每個練習題至少要包含2種運算符。

    4.所出的練習題在運算過程中不得出現負數與非整數。

    5.將學號與生成的n道練習題及其對應的正確答案輸出到文件“result.txt”中。

    6.支持有括號的運算式,包括出題與求解正確答案。算式中存在的括號必須大於2個,且不得超過運算符的個數。

    7.支持真分數的加減法,並且每個分數都化到最簡

 

二、功能設計

    能夠根據用戶輸入的參數n隨機產生n道符合要求的練習題,自動算出答案,並將式子與答案以文檔的形式呈現。並實現附加功能:支持有括號的運算、支持真分數的加減運算。

 

三、設計實現

    我共設計了5個類,如圖:

                   

    Creat類:負責隨機產生一條帶括號的至少2種運算符四則運算的式子,且有3-5個運算符

    Calculator類:負責篩選運算過程中不產生負數和小數的式子,並計算答案

    MakeFile類:負責產生result.txt文件,並將學號和產生的練習題寫入文件

    properFraction類:負責產生真分數式子並計算答案

    Main類:主類,負責接收命令行的參數並啟動程序

   5個類的相互調用關系為: 

             

 

    比較重要的函數:

        Creat類:creatProblem():隨機產生一條帶括號的含3-5個運算符四則運算式子,

                                若式子不符合條件,會遞歸直到產生符合條件的式子。

                 index(int n):產生運算符下標數組,並保證至少有2個不同的運算符。

        Calculator類:

                 algorithm(String s):結合了調度場算法和逆波蘭表達式的求值,計算出式子的答案。

                 calculate(int a,int b,String stmp):計算式子每一部分的運算,排除運算過程中出現小數和負數的式子。

        ProperFraction類:

                 createProblem():隨機產生一條含3-5個運算符的真分數加減運算式子,並計算出結果。

                 greatFactor(int x,int y):求最大公因數,用於化簡

 

    函數間的邏輯關系:creatProblem()調用index(int n)algorithm(String s)algorithm(String s)調用calculate(int a,int b,String stmp),還有生成文件相關的方法就不列舉了。

 

四、算法詳解

    本項目的關鍵在於Calculator類的計算,結合了調度場算法和逆波蘭表達式(即后綴表達式)的求值,一步實現計算四則運算式子。

    關於調度場算法和逆波蘭表達式求值,我花費了大量時間瀏覽博客理解它的實現過程,現總結如下:

    調度場算法的作用是將中綴表達式變為后綴表達式,它需要一個隊列來裝后綴表達式和一個棧來裝符號。先從左到右遍歷中綴表達式的每個符號和數字,若是數字就入隊;若是符號,則判斷其與棧頂符號的優先級,若該符號是右括號或其優先級低於或等於棧頂符號,則棧頂元素依次出棧並輸出進入隊列,並將當前符號進棧,一直到最終輸出后綴表達式。

    逆波蘭表達式求值步驟(只需要一個棧):

    1.先初始化一個空棧,開始遍歷后綴表達式。

    2.如果字符是一個操作數,則令其入棧。

    3.如果字符是個運算符,則彈出棧里的兩個操作數(一定會有兩個數在棧里,因為是后綴表達式),進行運算,再把結果入棧。

    4.到后綴表達式末尾,從棧中彈出結果。

   

    理解了這兩個算法后,就可將兩個算法結合,只需兩個棧就可一次性求出答案:

    1.初始化兩個棧,分別是數字棧和符號棧。

    2.遍歷中綴表達式,如果是數字,則入數字棧。

    3.如果是符號,則判斷其與符號棧的棧頂符號的優先級。若當前符號是右括號或其優先級低於或等於棧頂符號,則棧頂元素依次出符號棧,並在數字棧彈出兩個數進行相應運算,再使結果入數字棧,當前符號也入符號棧。

    4.當遇到等號,則將符號棧里的符號依次出棧,從數字棧彈出兩個數進行相應運算,再把結果入數字棧,直到最后一個符號出棧。把數字棧的數字彈出,即為結果。

   

    至於符號的優先級,則使用Hashmap<String,int>建立多組鍵值對,使每個符號對應一個數值,數值越高說明優先級越高。

    優先級從小到大:)小於 + - 小於 ×÷ 小於 (

 

五、測試運行

    進入src文件夾,在命令行輸入javac -encoding utf-8 Main.java 將類編譯成class文件,再輸入java Main 20 運行class文件,這里先做一個非法輸入和越界測試,如輸入java Main e或java Main 1200

  

    再正常輸入如java Main 20,將會在根目錄下(與src同級)產生result.txt文件:

   

    

          

    測試完成!

 

六、代碼展示

    產生整數式子的方法:

public static String createProblem(){
        Random random = new Random();
        String[] operator = {"+","-","×","÷"};

        int operatorCount = 3+random.nextInt(3); //操作符的個數3-5
        int[] num = new int[operatorCount+1]; //操作數的個數比操作符多1
        int[] index = index(operatorCount); //操作符的下標
        String s = new String();

        for(int j=0;j<operatorCount+1;j++){
            num[j] = random.nextInt(101); //產生0-100范圍的操作數,random.nextInt(n)的取值范圍是[0,n)
        }


        int choose = random.nextInt(2); //選擇式子括號形態

        switch (operatorCount){
            case 3:{
                if(choose==0){
                    s=num[0]+operator[index[0]]+"("+"("+num[1]+operator[index[1]]+num[2]+")"+operator[index[2]]+num[3]+")";//1+((2×3)-4)型
                }else s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×(3+4)型
                break;
            }

            case 4:{
                if(choose==0){
                    s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+num[4]+operator[index[3]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×3÷(4-1)型
                }else s=num[4]+operator[index[3]]+"("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//3×(1+2)+(4÷2)型
                break;
            }

            case 5:{
                if(choose==0){
                    s="("+num[0]+operator[index[0]]+num[1]+operator[index[4]]+num[5]+")"+operator[index[1]]+"("+num[4]+operator[index[3]]+num[2]+")"+operator[index[2]]+num[3];//(6+2×3)-(1+2)×3型
                }else s="("+num[0]+operator[index[0]]+"("+num[1]+operator[index[1]]+num[2]+operator[index[2]]+num[3]+")"+")"+operator[index[3]]+"("+num[4]+operator[index[4]]+num[5]+")";//(1+(2×3+4))-(6÷3)型
                break;
            }
        }


        s+="="; //給式子加上等號
        int answer = Calculator.calculate(s);

        if(answer>=0){ //判斷式子是否符合要求,凡是返回負數的就是不合格的
            s+=answer;
        }else {
            return createProblem(); //遞歸,直到產生合格的式子
        }

        return s;

    }

 

    保證式子里至少有2個不同操作符的方法:

    private static int[] index(int n,int m){ //產生操作符的下標數組
        Random random = new Random();
        int similar=0;
        int[] a = new int[n];
        for(int j=0;j<n;j++){
            a[j] = random.nextInt(m);
        }
        for(int j=1;j<n;j++){
            if(a[0]==a[j]) similar++;
        }
        if(similar==n-1) return index(n); //保證一個式子里至少有2個不同的操作符,若所有操作符下標都一樣,則重新產生操作符下標
        else {
            return a;
        }

    }

   

    產生真分數式子並計算的方法:

public String createProblem(){
        Random random = new Random();
        String[] operator = {"+","-"};
        int operatorCount = 3+random.nextInt(3); //操作符的個數3-5

        Create create = new Create();
        int[] index = create.index(operatorCount,2); //操作符的下標

        int sumx = 1+random.nextInt(10); //第一個數的分子1-10
        int sumy = 1+random.nextInt(10);//第一個數的分母1-10
        int greatFactor = greatFactor(sumx,sumy);
        sumx/=greatFactor; //化簡
        sumy/=greatFactor;

        while (sumx>=sumy){
            sumx = 1+random.nextInt(10);
            sumy = 1+random.nextInt(10);
            greatFactor = greatFactor(sumx,sumy);
            sumx/=greatFactor;
            sumy/=greatFactor;
        }

        String s=sumx+"/"+sumy; //第一個數

        for(int i=0;i<operatorCount;i++){
            int numx = random.nextInt(25); //分子分母不宜過大
            int numy = 1+random.nextInt(25); //否則通分可能會產生很大的數導致溢出
            String currentOpreator = operator[index[i]];
            while (numx>=numy){ //當分子大於分母,即假分數,則重新生成
                numx = random.nextInt(25);
                numy = 1+random.nextInt(25);
                greatFactor = greatFactor(numx,numy);
                numx/=greatFactor;
                numy/=greatFactor;
            }
            if(currentOpreator.equals("+")){  //加法
                while(sumx*numy+sumy*numx>sumy*numy) //和為假分數
                {
                    numx=random.nextInt(25);
                    numy=1+random.nextInt(25);
                    greatFactor=greatFactor(numx,numy);
                    numx/=greatFactor;
                    numy/=greatFactor;
                }
                sumx=sumx*numy+sumy*numx;
                sumy=sumy*numy;
            }
            else {   //減法
                while(sumx*numy-sumy*numx<0) //差為負數
                {
                    numx=random.nextInt(25);
                    numy=1+random.nextInt(25);
                    greatFactor=greatFactor(numx,numy);
                    numx/=greatFactor;
                    numy/=greatFactor;
                }
                sumx=sumx*numy-sumy*numx;
                sumy=sumy*numy;
            }
            s+=currentOpreator+numx+"/"+numy;
        }

        greatFactor = greatFactor(sumx,sumy);
        sumx/=greatFactor; //最終結果化簡
        sumy/=greatFactor;

        if(sumx==0) s+="="+sumx;
        else if(sumx==1&&sumy==1) s+="="+sumx;
        else s+="="+sumx+"/"+sumy;

        return s;

    }

 

    判斷非法輸入和越界輸入的方法(主方法):

public static void main(String[] args) {
        int n = 0;
        try {
             n = Integer.parseInt(args[0]);
            if(n>1000||n<1){
                System.out.println("對不起,只允許輸入1-1000的數字!");
                return; //結束運行
            }
        }catch (NumberFormatException e){ //輸入非數字字符等
            System.out.println("對不起,只允許輸入1-1000的數字!");
            return; //結束運行
        }

        MakeFile.creatFile(n);

    }

 

    其他代碼請見coding.net,就不一一展示了。

 

六、PSP

 

SP2.1

任務內容

計划共完成需要的時間(h)

實際完成需要的時間(h)

Planning

計划

26

47

·        Estimate

·   估計這個任務需要多少時間,並規划大致工作步驟

26

47

Development

開發

20

40

·        Analysis

·         需求分析 (包括學習新技術)

3

5

·        Design Spec

·         生成設計文檔

0

0

·        Design Review

·         設計復審 (和同事審核設計文檔)

0

0

·        Coding Standard

·         代碼規范 (為目前的開發制定合適的規范)

0

0

·        Design

·         具體設計

3

5

·        Coding

·         具體編碼

10

15

·        Code Review

·         代碼復審

2

5

·        Test

·         測試(自我測試,修改代碼,提交修改)

2

10

Reporting

報告

6

7

·         Test Report

·         測試報告

5

6

·         Size Measurement

·         計算工作量

0.5

0.5

·         Postmortem & Process Improvement Plan

·         事后總結, 並提出過程改進計划

0.5

0.5

 

 

七、總結

    這次項目比我想象中的要難,原以為一兩天就可以搞定,實際上花了整整4天時間在圖書館鑽研。其實做這個項目我並沒有完整地按照軟件開發的步驟,也沒有提前設計代碼,想到哪就寫到哪,導致敲代碼的過程中遇到很多小問題,一遇到問題就得停下來去找相應的解決方法,經常是代碼寫了又刪,刪了又寫,效率比較低,下次一定要事先設計,想好整個流程再寫代碼。此外,通過這次作業,我感受到了算法在項目中的重要性,算法是一個項目的靈魂。就如調度場算法和逆波蘭表達式求值算法是這次作業的核心,我再也不敢說類似於不知算法有什么用之類的話了。

    原本我是沒有實現分數加減的附加功能的,因為我潛意識里覺得它很難,所以壓根沒想過也不敢做這個附加功能。但是看了其他同學的博客后,發現很多同學都實現了,我分析他們的代碼,突然覺得也不是很難了,幾經思考后我最終也實現了分數的功能,開心不已。我想寫博客的意義就在此吧,互相分享自己的學習成果,共同進步。很感謝那些願意寫博客分享技術的人,從他們的博客中真的可以學到很多東西!

    這4天,看了一篇又一篇博客,改了一段又一段代碼,很疲憊卻也很充實,讓我感受到了全身心投入一件事情的快樂,專注與鑽研,我喜歡這樣的感覺。同時,也讓我意識到自己的水平遠比想象中的低,做一個四則運算就讓我費了這么大的勁,說明我的水平真的還不夠,我要好好努力。最后,還是忍不住分享獨自完成一個小項目的喜悅,真的很開心,也給了我很大的鼓勵!專注的感覺真好!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM