1. 程式人生 > >個人作業1——四則運算題目生成程序(基於控制臺)

個人作業1——四則運算題目生成程序(基於控制臺)

deb nio body min 此外 list eve span i++

一、需求分析

  • 生成四則運算題目
  • 控制生成題目個數
  • 控制生成題目中數字的範圍
  • 結果為真分數
  • 每道題目運算符個數為3
  • 每次運行生成的題目不能重復
  • 保存生成的題目
  • 在生成題目的同時,計算出所有題目的答案,並保存
  • 支持一萬道題目的生成
  • 支持題目判錯,並保存統計結果

二、功能設計

  • 基本功能
  1. 每個數字定義為一個包含分子、分母、符號的結構體
  2. 隨機生成數字
  3. 隨機生成四則運算符號
  4. 按照所有可能出現的情況隨機生成式子
  5. 在重新定義的數字上實現通分、約分、以及加減乘除的基本操作
  6. 將生成的式子轉化成後綴表達式
  7. 通過後綴表達式計算式子的值
  8. 再生成式子時進行查重
  9. 將生成的式子以及計算出的答案保存到txt文件
  10. 對提供的式子和答案進行打分校驗,包括查重,並將統計結果保存到txt文件
  11. 通過命令行傳遞參數
  • 擴展功能
  1. 解決加括號的意義問題(即括號內的符號至少有一個+或-)
  2. 完善命令行傳遞參數的各種情況處理,並提供幫助選項
  3. 對結果為負數的情況進行了處理

三、設計實現

  • 我的整體思路是將一個數看做一個有分子、分母和符號的結構體,然後在這個基礎上進行生成和各種運算。數字在內存中一律是真分數,只有在需要將其輸出成字符串時,再將其轉換成對應的整數、帶分數、真分數。而加上一個名為符號的成員變量是為了處理運算結果出現負數的情況。
  • 關於查重功能的實現,我分為兩種。一種是在生成式子時的查重,此時只需要將式子對應的後綴表達式進行排序後存到一個map<string, int>中,其key為後綴表達式排序後的字符串,並將其值置為1,之後遇到計算結果相同的式子只需要判斷其後綴表達式排序後的字符串對應的map中的值是否為1即可知道是否重復。另一種是在對用戶提供的式子進行查重時,判斷的方式與上面相同;區別在於由於需要獲得兩個重復式子的位置和內容,所以需要將前一個式子的位置信息和自身提前存起來,等後一個式子判斷與其重復時,再將其信息返回,這樣就可以將兩個式子同時存到另一個地方,最後一起輸出。
  • 項目主要包括Math.h、Calculator.h、PrintAndFile.h三個頭文件
  1. Math.h主要封裝了數字結構體的定義和一些相關的計算函數
  2. Calculator.h主要封裝了生成數字、生成運算符、生成式子、將式子轉換成後綴表達式和計算後綴表達式的值等函數
  3. PrintAndFile.h主要封裝了打印命令行提示以及一些讀文件和存文件的相關函數,但是不知道為什麽一把它分出來就會編譯出錯,所以就只好把它寫在main函數裏面了

四、代碼說明

  • 生成數字的代碼如下:
    //生成Number
    Number GenerateNumber(int numMax){
        int isInt, RealNumerator, IntNumber;
        Number num;
    
        num.sign 
    = 1; isInt = rand() % 2; if (isInt){ num.denominator = 1; num.numerator = rand() % numMax + 1; } else{ num.denominator = rand() % numMax + 2; RealNumerator = rand() % (num.denominator - 1) + 1; //約分 int common = CommonDivisor(num.denominator, RealNumerator); num.denominator = num.denominator / common; RealNumerator = RealNumerator / common; IntNumber = rand() % (numMax - 1); num.numerator = IntNumber*num.denominator + RealNumerator; } return num; }

  • 將字符串轉換為Number的代碼如下,主要思想是通過定位特殊符號的位置來進行轉換:
    //將字符串轉為Number
    Number Str2Num(string str){
        Number num;
        int optPosi[2] = { 0, 0 };
    
        for (int i = 0; i < str.size(); i++){
            if (str[i] == 39){
                optPosi[0] = i;
            }
            else if (str[i] == 47){
                optPosi[1] = i;
            }
        }
        if (optPosi[0] == 0 && optPosi[1] == 0){
            int tem = 0;
            for (int i = str.size(), j = 0; i > 0; i--, j++){
                tem = tem + (str[j] - 48) * pow(10, i - 1);
            }
            num.numerator = tem;
            num.denominator = 1;
            num.sign = 1;
            return num;
        }
        else if (optPosi[0] == 0 && optPosi[1] != 0){  //有 真分數 1/2
            int tem = 0;
            for (int i = optPosi[1], j = 0; i > 0; i--, j++){
                tem = tem + (str[j] - 48) * pow(10, optPosi[1] - j - 1);
            }
            num.numerator = tem;
            tem = 0;
            for (int i = str.size(), j = optPosi[1] + 1; i > j; i--, j++){
                tem = tem + (str[j] - 48) * pow(10, str.size() - j - 1);
            }
            num.denominator = tem;
            num.sign = 1;
        }
        else{               //有帶分數 1‘2/3
            int tem1 = 0, tem2 = 0, temInt = 0;
            for (int i = optPosi[0], j = 0; i > 0; i--, j++){
                temInt = temInt + (str[j] - 48) * pow(10, i - 1);
            }
            temInt = temInt;
            for (int i = optPosi[1], j = optPosi[0] + 1; i >= j; i--, j++){
                tem1 = tem1 + (str[j] - 48) * pow(10, optPosi[1] - j - 1);
            }
            for (int i = str.size(), j = optPosi[1] + 1; i >= j; i--, j++){
                tem2 = tem2 + (str[j] - 48) * pow(10, str.size() - j - 1);
            }
            num.denominator = tem2;
            num.numerator = temInt * tem2 + tem1;
            num.sign = 1;
        }
    
        return num;
    }

  • 查重的代碼如下:
    //校驗時查重
    bool Check(string que, string queStr, map<string, int> &queList, map<string, Question> &QueList, int posi, Question &Que){
        Question queStruct;
        sort(que.begin(), que.begin() + que.size());
        if (queList.empty() || queList[que] == NULL){
            queList[que] = 1;
            queStruct.no = posi;
            queStruct.queStr = queStr;
            QueList[que] = queStruct;
            return false;
        }
        else{
            Que = QueList[que];
            return true;
        }
    }
    
    
    //生成時查重
    bool Check(string que, map<string, int> &queList){
        sort(que.begin(), que.begin() + que.size());
        if (queList.empty() || queList[que] != 1){
            queList[que] = 1;
            return false;
        }
        else{
            return true;
        }
    }

  • 將式子轉換成後綴表達式的代碼如下,是通過棧實現的,通過比較不同符號棧內和棧外的優先級來決定是入棧還是出棧:
    //生成後綴表達式
    void GeneratePostfix(string que, list<string> &quePostfix, stack<char> &optStack){
        map<char, int> isp, icp;
        isp[+] = 3; isp[-] = 3;
        isp[*] = 5; isp[/] = 5;
        isp[(] = 1;  isp[)] = 6;
        icp[+] = 2; icp[-] = 2;
        icp[*] = 4; icp[/] = 4;
        icp[(] = 6;  icp[)] = 1;
        isp[#] = 0; icp[#] = 0;
    
        string temPostfix;
        char optNow;
    
        que = que + #;
        optStack.push(#);
    
        for (int i = 0; i < que.size(); i++){
            if (que[i] > 47 && que[i] < 58){
                temPostfix = temPostfix + que[i];
                if (que[i + 1] == 39){
                    temPostfix = temPostfix + que[i + 1] + que[i + 2] + que[i + 3] + que[i + 4];
                    i = i + 4;
                }
                else if (que[i + 1] == 47){
                    temPostfix = temPostfix + que[i + 1] + que[i + 2];
                    i = i + 2;
                }
                if (que[i + 1] < 48){
                    temPostfix = temPostfix + " ";
                }
                continue;
            }
            else if (que[i] < 0){
                optNow = /;
                i++;
            }
            else{
                optNow = que[i];
            }
    
            while (isp[optStack.top()] > icp[optNow]){
                if (optStack.top() == 47){
                    temPostfix = temPostfix + "÷ ";
                }
                else{
                    temPostfix = temPostfix + optStack.top() + " ";
                }
                optStack.pop();
            }
            if (isp[optStack.top()] == icp[optNow]){
                optStack.pop();
            }
            else if (isp[optStack.top()] < icp[optNow]){
                optStack.push(optNow);
            }
    
        }
        quePostfix.push_back(temPostfix);
    }

  • 計算表達式的代碼如下,也是通過棧來實現的:
    //計算表達式
    void Calculate(string que, list<string> &ans, stack<Number> &ansStack, bool isError){
        Number num;
        string temStr;
        char optNow;
    
        for (int i = 0; i < que.size(); i++){
            while (que[i] !=  ){
                temStr = temStr + que[i];
                i++;
            }
            if (que[i] =  ){
                if (temStr[0] > 47 && temStr[0] < 58){
                    num = Str2Num(temStr);
                    ansStack.push(num);
                    temStr = "";
                    continue;
                }
                else if (temStr[0] < 0){
                    Number num1, num2;   //除法
                    num2 = ansStack.top();
                    ansStack.pop();
                    num1 = ansStack.top();
                    ansStack.pop();
                    num = Division(num1, num2);
                    ansStack.push(num);
                    temStr = "";
                }
                else if (temStr[0] == 42){  //乘法
                    Number num1, num2;
                    num2 = ansStack.top();
                    ansStack.pop();
                    num1 = ansStack.top();
                    ansStack.pop();
                    num = Multiplication(num1, num2);
                    ansStack.push(num);
                    temStr = "";
                }
                else if (temStr[0] == 43){  //加法
                    Number num1, num2;
                    num2 = ansStack.top();
                    ansStack.pop();
                    num1 = ansStack.top();
                    ansStack.pop();
                    num = Addition(num1, num2);
                    ansStack.push(num);
                    temStr = "";
                }
                else if (temStr[0] == 45){  //減法
                    Number num1, num2;
                    num2 = ansStack.top();
                    ansStack.pop();
                    num1 = ansStack.top();
                    ansStack.pop();
                    num = Subtraction(num1, num2);
                    ansStack.push(num);
                    temStr = "";
                }
    
            }
        }
        if (ansStack.top().denominator == 0){
            isError = true;
        }
        else{
            string a = Num2Str(ansStack.top());
            ans.push_back(a);
            ansStack.pop();
        }
    
    }

五、測試運行

  • 命令行參數提示
  • 技術分享
  • 參數出錯提示
  • 技術分享
  • 進行出題

  • 技術分享

  • 技術分享

  • 打分校驗結果
  • 技術分享
  • 技術分享

六、PSP展示

PSP2.1

Personal Software Process Stages

Time Senior Student

Time

Planning

計劃

10

7

· Estimate

估計這個任務需要多少時間

10

7

Development

開發

325

903

· Analysis

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

10

20

· Design Spec

生成設計文檔

5

0

· Design Review

設計復審

5

0

· Coding Standard

代碼規範

5

3

· Design

具體設計

60

30

· Coding

具體編碼

300

655

· Code Review

代碼復審

10

5

· Test

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

30

190

Reporting

報告

60

60

·

測試報告

40

Nah

·

計算工作量

10

Nah

·

並提出過程改進計劃

10

Nah

七、小結

這次作業第一次開始接觸到PSP來管理項目開發,讓我在開發過程中效率提高了不少,雖然預估和實際差的還是挺大,但是相對自己之前而言,效率已經提高了不少,之後我會盡可能把這種方式運用到其實事情上來提高自己效率。

此外這次作業的內容看起來並不是很難,但是真正做起來的時候還是很花時間的,尤其是在一些細節的地方,其實有一大部分時間都是在調試、debug,還好最終問題都得到了解決,最後的效果也算是比較完整,個人感覺比較有缺陷的還是代碼的質量上,感覺自己寫的代碼質量不高,一些東西沒有設計好,希望以後可以不斷提高自己的代碼質量。

最後附上代碼地址:https://coding.net/u/DaleAG/p/Calculator/git/tree/master/

個人作業1——四則運算題目生成程序(基於控制臺)