1. 程式人生 > >高級軟件工程2017第2次作業

高級軟件工程2017第2次作業

入棧 改進 icop har 工作 生成 很大的 strcmp def

1.github項目地址:

軟件工程第二次作業鏈接

2.解題思路及設計過程

剛開始拿到題目後,看到要求上寫著:

完成一個能自動生成小學四則運算題目的命令行 “軟件”

首先想到的是用rand()函數生成操作數和運算符,然後先從簡單地只生成兩個操作數的四則運算式,並且只考慮整數之間的運算。實現前面一步,再看題目運算符要求三個以上,這就需要用數組來存放操作數和運算符,同樣很容易能隨機生成多個操作數的四則運算式。接著遇到了最困難的一步:

要求能處理用戶的輸入,並判斷對錯,打分統計正確率。

要能判斷用戶輸入的對錯就必須計算四則運算式的結果,一開始想到既然操作數和運算符都存放在數組裏了,那麽直接遍歷數組,根據操作符將操作數取出計算。結果發現由於需要考慮運算的優先級,用數組操作過程極為復雜,很難計算出正確結果。於是從網上查找資料,發現用後綴表達式計算四則運算算法思路簡單清晰。共需要準備三個容器,符號棧operatorStack,數字棧numStack和盛放後綴表達式的隊列expQueue。按照下列的規則進行:

  • 用for循環,每次隨機生成一個操作數或運算符並打印
  • 若產生操作數num,就expQueue.push(num);
  • 若產生運算符,分以下兩步處理:
  • 1.如果當前運算符優先級不高於符號棧棧頂運算符,則彈出操作符op,並做expQueue.push(op)操作;
  • 2.將當前運算符op放入棧中,operatorStack.push(op);
  • 當上述操作完成,表示打印出運算式並生成一個後綴表達式,接下來計算後綴表達式
  • 遇到數字num,numStack.push(num);
  • 若遇到運算符,從numStack棧頂取出兩個操作數並計算,結果放入棧頂
  • 不斷進行上面兩步操作,最後只剩一個數字,就是最終結果

由於對棧和隊列的操作不夠熟練,並且以上只用到了基本操作,所以決定用數組來代替棧和隊列,通過下標變換可以實現同樣的效果。不過又遇到一個問題,那就是盛放後綴表達式的expQueue數組需要同時存放操作數和運算符,兩者類型不同無法用數組存放。經過嘗試,想出一個方法那就是:把操作數單獨存放到數組裏面,而在expQueue中存放該操作數對應的下標。完成以上功能後,還需解決題目的最後一個需求:

參與運算的操作數(operands)除了100以內的整數以外,還要支持真分數的四則運算,例如:1/6 + 1/8 = 7/24。

看到這個要求,一時之間又遇到了難題:一個真分數需要考慮分子和分母兩個操作數,如果支持真分數的四則運算,那麽不如把所有操作都想轉換成分數,最後對結果進行化簡,那麽問題就轉化為如何處理兩個分數的運算?經過反復思考,想到一個令自己比較滿意的解決方法,那就是使用結構體,定義兩個成員變量分別代表分子、分母。完成以上的設計分析再開始編程,發現代碼的編寫十分順利!

3.代碼說明:

基本功能

1.返回操作符的優先級

技術分享
int mPriority(char op){
    if(op==
+) return 1; else if(op==-) return 1; else if(op==x) return 2; else if(op==%) return 2; }
mPriority

2.求最大公約數

技術分享
int gcd(int a,int b)
{
    if(b==0) return a;
    else return gcd(b,a%b);    
}  
gcd

3.根據操作符計算兩個操作數

技術分享
Digit Calculate(Digit num1,Digit num2,char op){
    Digit res;
    int f;
    //根據運算符,作兩個分數之間的四則運算 
    switch(op){
        case +:{
            res.x=num1.x*num2.y+num1.y*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case -:{
            res.x=num1.x*num2.y-num1.y*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case x:{
            res.x=num1.x*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case %:{
            res.x=num1.x*num2.y;
            res.y=num1.y*num2.x;
            break;
        }
        default:{
            res.x=0;
            res.y=1;
        }
    }
    if(res.x<res.y) 
        f=gcd(res.y,res.x);
    else 
        f=gcd(res.x,res.y);
    res.x=res.x/f;res.y=res.y/f;
    //如果分母為負數,取反
    if(res.y<0){
        res.x=-res.x;
        res.y=-res.y;
    } 
    return res;
} 
Calculate

4.隨機生成1個操作數並打印

技術分享
Digit getNum()
{
    int i,j,f;
    Digit res;
    if(rand()%3==1)//1/3的概率生成一個真分數 
    {
        i=rand()%11+1;
        j=rand()%11+1;
        if(i>j) {int temp=i;i=j;j=temp;}
        f=gcd(j,i);
        i=i/f;j=j/f;
        printf("%d/%d",i,j);
    }else{
        i=rand()%101+1;
        j=1;
        printf("%d",i);
    }
    res.x=i;
    res.y=j;
    return res;
}
getNum

5.隨機生成一個操作符並打印

技術分享
char getOperator(){
    char op=ss[rand()%4];
    //打印操作符 
    if(op==%) 
        printf("÷");
    else if(op==x)
        printf("×");
    else printf("%c",op);
    return op;
} 
getOperator

主函數

技術分享
int main()
{
    srand((unsigned)time(NULL));  //每次運行進行初始化 
    int times; //控制生成題目的個數 
    float score=100; //題目得分 
    scanf("-n %d",&times);
    printf("本次共%d題,滿分100分\n",times);
    //第一個for循環,每次生成一個題目 
    for(int j=0;j<times;j++){
        printf("%d: ",j+1);
        int t=0,q=0,p=0,top=0;
        Digit opNum[10],numStack[10];
        char op,operatorStack[10],expQueue[20];
        opNum[q++]=getNum();
        expQueue[p++]=q-1+0;
        //得到後綴表達式 
        for(t=0;t<3;t++)
        {
            op=getOperator();    
            if(t==0){
                operatorStack[top++]=op;
                opNum[q++]=getNum();
                expQueue[p++]=q-1+0;
                continue;
            }
            while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0){
                top--;
                expQueue[p++]=operatorStack[top];
            }
            opNum[q++]=getNum();
            expQueue[p++]=q-1+0;
            operatorStack[top++]=op;
        } 
        while(top>0){
            top--;
            expQueue[p++]=operatorStack[top];
        }
        //根據後綴表達式計算結果 
        top=0;
        for(int i=0;i<p;i++){
            if(expQueue[i]>=0&&expQueue[i]<=9)
                {
                    int ch=expQueue[i]-0;
                    numStack[top].x=opNum[ch].x;
                    numStack[top].y=opNum[ch].y;
                    top++;
                }else{
                    top--;
                    numStack[top-1]=Calculate(numStack[top-1],numStack[top],expQueue[i]);
                }
        }
        printf("=");
        //用戶輸入計算結果
        char userAns[100],rightAns[100];
//        printf("%d/%d\n",numStack[top-1].x,numStack[top-1].y);
        cin>>userAns;
        int c=getchar();
        //得到的正確結果 
        if(numStack[top-1].y!=1){
            sprintf(rightAns,"%d%s",numStack[top-1].x,"/");
            sprintf(rightAns,"%s%d",rightAns,numStack[top-1].y);
        }    
        else 
            sprintf(rightAns,"%d",numStack[top-1].x);
        //printf("%s\n",rightAns);
        //判斷對錯 
        if(strcmp(userAns,rightAns)==0)
            printf("正確!\n");
        else {
            printf("不正確!正確答案= %s\n",rightAns);
            //扣分
            score-=100*1.0/times; 
        }
        
    }
    printf("本次得分%.2f",score);    
    return 0;    
} 
Main

附加功能(9/25改進)

上面代碼只實現了基本功能,對於附加功能運算符個數隨機生成,只需改變t的值即可。至於帶括號的多元復合運算,整個代碼的思路沒有改變,特別對後綴表達式的計算部分不用作任何變動。在得到後綴表達式的過程,分別在運算符之後隨機生成左括號,在合適的操作數之後生成相應的右括號,另外,需要在符號棧的進出棧作一些改變。對代碼作改進的部分如下:

技術分享
//加入附加功能,運算符個數隨機生成
        int op_Num=rand()%5+1; 
        //附加功能,控制括號的生成
        int braket_Max=2,braket=0,flag=0;//分別代表生成括號的個數和當前左括號個數 
        //得到後綴表達式 
        for(t=0;t<op_Num;t++)
        {
            op=getOperator();  //生成運算符  
            if(t==0){
                operatorStack[top++]=op;
                 //隨機決定是否生成左括號
                if(rand()%3==1&&t<op_Num-1){
                    printf("(");//打印括號 
                    braket_Max--;
                    braket++;
                    operatorStack[top++]=(; //左括號入棧 
                }
                opNum[q++]=getNum();
                expQueue[p++]=q-1+0;
                continue;
            }
           
            //當符號棧頂不是左括號,根據優先級判斷出棧 
            if(operatorStack[top-1]!=(){
                while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0&&operatorStack[top-1]!=(){
                    top--;
                    expQueue[p++]=operatorStack[top];
                }
            }
            operatorStack[top++]=op;
             //隨機決定是否生成左括號
            if(rand()%3==1&&t<op_Num-1){
                if(braket_Max<0) break;//如果已經生成三對括號,就不再生成 
                printf("(");//打印括號 
                flag=t;
                braket_Max--;
                braket++;
                operatorStack[top++]=(; //左括號入棧 
            }
            opNum[q++]=getNum();//產生一個隨機數 
            expQueue[p++]=q-1+0;
            //隨機決定是否生成右括號
            if(flag!=t&&rand()%3==1){
            
                if(braket<=0) break; 
                printf(")");//打印右括號
                braket--;
                //一直出棧直到遇到左括號
                while(operatorStack[top-1]!=() {
                    top--;
                    expQueue[p++]=operatorStack[top];
                }
                top--;
            }
        } 
        //如果還有左括號還未匹配
        while(braket>0){
            braket--;
            printf(")");
            while(operatorStack[top-1]!=() {
                    top--;
                    expQueue[p++]=operatorStack[top];
            }
            top--;
        } 
        while(top>0){
            top--;
            expQueue[p++]=operatorStack[top];
        }
View Code

4. 遇到了一些問題:

計算結果為分數可能在分母出現負號。

  • 對分子、分母求最大公約數,當最大公約數為負數,化簡後分母為負數,這就需要判斷:如果分母小於零,分子、分母同時取反。
if(res.y<0){
        res.x=-res.x;
        res.y=-res.y;
    } 

還有各種小問題,就不一一敘述了。

5.測試運行

根據需要在-n後面輸入要生成的題目個數,每個運算式後面輸入自己的計算結果,由程序自動判斷對錯。

基本功能:

技術分享

支持附加功能

技術分享

6.PSP

PSP2.1Personal Software Process Stages預估耗時(分鐘)實際耗時(分鐘)
Planning 計劃 10   5
· Estimate · 估計這個任務需要多少時間 10 5
Development 開發 600 695
· Analysis · 需求分析 (包括學習新技術) 60 50
· Design Spec · 生成設計文檔 10 30
· Design Review · 設計復審 (和同事審核設計文檔) 10 10
· Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 10 5
· Design · 具體設計 120 300
· Coding · 具體編碼 200 180
· Code Review · 代碼復審 30 20
· Test · 測試(自我測試,修改代碼,提交修改) 170 100
Reporting 報告 250 210
· Test Report · 測試報告 120 100
· Size Measurement · 計算工作量 30 20
·Postmortem& Process Improvement Plan · 事後總結, 並提出過程改進計劃 100 90
合計 860 910

7.心得體會

第一次做軟件工程項目,雖然是一個小項目,但是也學到了軟件開發的一部分流程。體會比較深的就是,在開發的過程中把更多的時間投入的開發設計和測試上,而不是把重點放在代碼的編寫上。用更多的時間構思,把程序需要解決的問題考慮清楚,這樣寫起代碼思路十分清晰,不會出現代碼混亂,確實提高了開發效率。另外,通過這次作業學習了GitHub的基本用法,知道了如何使用GitHub管理自己的項目,給自己的開發有很大的幫助。

高級軟件工程2017第2次作業