零、github地址:
https://github.com/King-Authur/-Automatically-generate-four-arithmetic-problems
一、項目的相關要求
實現一個自動生成小學四則運算題目的命令行程序(也可以用圖像界面,具有相似功能)。
項目需求
- 使用 -n 參數控制生成題目的個數,例如
Myapp.exe -n 10
將生成10個題目。
- 使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的範圍,例如
Myapp.exe -r 10
將生成10以內(不包括10)的四則運算題目。該參數可以設置爲1或其他自然數。該參數必須給定,否則程序報錯並給出幫助信息。
-
生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1− e2的子表達式,那麼e1≥ e2。
-
生成的題目中如果存在形如e1÷ e2的子表達式,那麼其結果應是真分數。
-
每道題目中出現的運算符個數不超過3個。
-
程序一次運行生成的題目不能重複,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換爲同一道題目。
例如:
23 + 45 = 和45 + 23 = 是重複的題目
6 × 8 = 和8 × 6 = 也是重複的題目。
3+(2+1)和1+2+3這兩個題目是重複的,由於+是左結合的,1+2+3等價於(1+2)+3,也就是3+(1+2),也就是3+(2+1)。
但是1+2+3和3+2+1是不重複的兩道題,因爲1+2+3等價於(1+2)+3,而3+2+1等價於(3+2)+1,它們之間不能通過有限次交換變成同一個題目。
生成的題目存入執行程序的當前目錄下的Exercises.txt文件,格式如下:
1.四則運算題目1
2.四則運算題目2
……
其中真分數在輸入輸出時採用如下格式,真分數五分之三表示爲3/5,真分數二又八分之三表示爲2’3/8。
- 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件,格式如下:
1.答案1
2.答案2
特別的,真分數的運算如下例所示:1/6 + 1/8 = 7/24。
-
程序應能支持一萬道題目的生成。
-
程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計,輸入參數如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
統計結果輸出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”後面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。爲簡單起見,假設輸入的題目都是按照順序編號的符合規範的題目。
二、遇到的困難和解決方法
(一)如何進行結對編程
在項目開始前我們進行了時長爲一個多小時的討論,理清了思路並對項目進行了整體設計和規劃,隨後我們分配好各自要負責的模塊,並寫好了接口文件進行項目的開發。
在編程的過程中,我們遇到有疑惑或者覺得思路上有出入的地方也會在微信上及時進行溝通,保證了項目的正常推進。
完成代碼編寫之後,我們先各自檢查了自己的代碼、測試模塊,隨後將代碼整合起來,發送給對方讓對方進行代碼複審。在實際操作中,我們都爲對方發現了許多的bug。
(二)如何存儲數據
爲了能夠較爲簡易的實現對分數的存儲和計算等操作,我們經過討論,決定選擇用結構體來存儲,並對結構體的部分運算符進行重載。
三、關鍵代碼 和 設計說明
整體設計
數據的定義
typedef struct variable
{
int num_or_Symbol; //0是數字1是符號
int Symbol = -1; // + - * % ( ) 分別表示爲 0 1 2 3 4 5
int numer; //如果是數字此爲分子
int Den = 1; //如果是數字此爲分母
int num; //如果是數字此爲分數前的係數
bool operator == (variable c){
return num_or_Symbol == c.num_or_Symbol && Symbol == c.Symbol && numer == c.numer && Den == c.Den && num == c.num;
}
}var;
主要的幾個部分如下
//生成表達式函數
Status Create(var** exp, int size, int *length);
//計算表達式函數
Status Calculation(var* exp, int size, var* result, int length);
//中綴表達式轉後綴表達式
Status Infix_to_Postfix(var* p, int size, var* Postfix, int length, int& postLen);
//判斷兩個問題是否等價
Status is_question_same(var* Question, int lenQuest, var* newQuestion, int lenNewQuest, int size);
//m指令的執行
void M_instructions(var** expression, int amount, int size, var* result);
//判斷對錯
void Correction(int* save, char* answerfile, char* exercisefile);
關鍵代碼
創建題目
Status Create(var** exp, int size, int* length)
{
var* expre;
int mark_num = random(1, 4);//計算符個數
int pre = 0;//前括號在第pre個數字前
int aft = 0;//後括號在第aft個數字後
int judge = 0;//判斷,0寫入數字,1寫入符號
int n = 0;
*length = mark_num + mark_num + 1;
n = 0;
if (mark_num > 1)//如果運算符有3個,則存在括號
{
pre = random(1, mark_num);
if(pre == 1)//不讓括號括住整個式子
aft = random((pre + 1), (mark_num + 1));
else
aft = random((pre + 1), (mark_num + 2));
(*length) += 2;
expre = new var[*length + 1];
expre[pre * 2 - 2].num_or_Symbol = 1;
expre[pre * 2 - 2].Symbol = 4;
expre[aft * 2].num_or_Symbol = 1;
expre[aft * 2].Symbol = 5;
}
else
{
expre = new var[*length + 1];
}
n = 0;
while (n < *length)
{
if (expre[n].Symbol < 4)
{
if (judge == 0)
{
expre[n].num_or_Symbol = 0;
expre[n].Den = random(2, size);
expre[n].numer = random(0, expre[n].Den);
expre[n].num = random(1, size);
judge = 1;
}
else
{
expre[n].num_or_Symbol = 1;
expre[n].Symbol = random(0, 4);
judge = 0;
}
}
n++;
}
*exp = expre;
return SUCCESS;
}
中綴表達式轉後綴表達式
Status Infix_to_Postfix(var* p, int size, var* Postfix, int length, int& postLen)
{
//傳入的postfix要記得爲空
var stack[maxn];
int top = 0;
for (int i = 0; i < length; i++)
{
if (p[i].num_or_Symbol == 0)//是數字
{
Postfix[postLen++] = p[i];//放入輸出串中
}
if (p[i].num_or_Symbol == 1 && p[i].Symbol == 4)//左括號
{
++top;
stack[top] = p[i];
}
while (p[i].num_or_Symbol == 1 && p[i].Symbol != 4 && p[i].Symbol != 5)
{
if (top == 0 || stack[top].Symbol == 4 || prio(p[i]) > prio(stack[top]))
{
++top;
stack[top] = p[i];
break;
}
else
{
Postfix[postLen++] = stack[top];
top--;
}
}
if (p[i].num_or_Symbol == 1 && p[i].Symbol == 5)//右括號
{
while (stack[top].Symbol != 4)
{
Postfix[postLen++] = stack[top];
top--;
}
top--;
}
}
while (top != 0)
{
Postfix[postLen++] = stack[top--];
}
return SUCCESS;
}
判斷題目是否等價
Status is_problem_same(var* Question, int lenQuest, var* newQuestion, int lenNewQuest, int size)
{
var Postfix1[maxn], Postfix2[maxn];
var stack1[3][3], stack2[3][3];
int len1 = 0, len2 = 0, sta_size1 = 0, sta_size2 = 0;
//獲取後綴表達式
Infix_to_Postfix(Question, size, Postfix1, lenQuest , len1);
Infix_to_Postfix(newQuestion, size, Postfix2, lenNewQuest, len2);
//獲取子表達式
get_Subexpression(Postfix1, len1, stack1, sta_size1);
get_Subexpression(Postfix2, len2, stack2, sta_size2);
bool flag;
for (int i = 0; i < sta_size1; i++)
{
flag = false;
for (int j = 0; j < sta_size2; j++)
{
//短式等價
if (cmp(stack1[i], stack2[j]))
{
flag = true;
stack2[j][2].Symbol = -1;//將表達式的運算符刪掉
break;
}
}
if (!flag)//如果存在不一樣的,返回not same
{
return ERROR;
}
}
return SUCCESS;
}
m操作
void M_instructions(var **expression, int amount, int size, var* result)
{
fstream answer;
answer.open(ANSWERFILE, ios::out | ios::app);
var results[maxn];//後綴表達式
int length;
int i = 0;
int j = 0;
int k = 0;
while (i < amount)
{
Create(&expression[i], size, &length);
result[i].Symbol = length;
if (Calculation(expression[i], size, results, length) == ERROR || results[0].num >= size || results[0].numer >= size || results[0].Den >= size)
{
continue;
}
result[i].Den = results[0].Den;
result[i].num = results[0].num;
result[i].numer = results[0].numer;
result[i].num_or_Symbol = 0;
result[i].Symbol = length;
j = 0;
while (j < i)
{
//結果一樣,表達式可能一樣
if (result[j].Den == result[i].Den && result[j].numer == result[i].numer && result[j].num == result[i].num)
{
if (is_question_same(expression[i], result[i].Symbol, expression[j], result[j].Symbol, size))
{
break;
}
}
j++;
}
if (i != j)
{
if(k ++ < 20)//連續20次重複答案表明給的size太小,而amount太大,表達式多樣性不足
continue;
}
Visit(expression[i], length, i + 1);
answer << i + 1 << ". ";
if (result[i].numer == 0)
{
answer << result[i].num;
}
else
{
if (result[i].num != 0)
{
answer << result[i].num;
answer << "`";
}
answer << result[i].numer;
answer << "/";
answer << result[i].Den;
}
answer << endl;
i++;
k = 0;
}
answer.close();
}
四、測試運行
隨機生成10道題目
改變題目數值後隨機生成10道題目
測試在exercisefile.txt文件內填入答案後判斷對錯
五、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 20 | 20 |
· Estimate | · 估計這個任務需要多少時間 | 10 | 10 |
·Development | ·開發 | 120 | 100 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | 60 |
· Design Spec | · 生成設計文檔 | 20 | 20 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 10 | 10 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 5 | 10 |
· Design | · 具體設計 | 30 | 50 |
· Coding | · 具體編碼 | 180 | 200 |
· Code Review | · 代碼複審 | 20 | 15 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 20 | 20 |
Reporting | 報告 | 30 | 30 |
· Test Report | · 測試報告 | 20 | 15 |
· Size Measurement | · 計算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 30 | 30 |
合計 | 585 | 600 |
六、項目小結
我的小結:
1、在本次項目中,我們通過結對編程,互相鼓勵,相互監督,以更高的效率完成了本次任務,充分的認識到了結對編程的益處,理解它在編程效率與錯誤檢查上的巨大作用。當然也瞭解到了它的缺點,尤其是在疫情期間溝通不便的客觀環境下,溝通的難度也大大增加,但多虧網絡會議的共享屏幕功能、微信電話的便捷、雙方的耐心溝通和積極討論等因素,讓結對編程的缺點方面也得到了極大的補足。
2、在項目過程中,我學習了中綴表達式轉後綴表達式的方法、計算後綴表達式的規則、判斷兩個題目是否等價的方法等知識,還向搭檔學習瞭如何寫更加規範的接口文件,收貨頗豐。
3、另外圖形化界面目前還在學習,只能做出簡單的界面,未能完成項目擴展部分的需求,這是一個小小的遺憾,還需激勵自己更快的學習和掌握知識,更好地完成項目需求。
4、最後再次感謝我的搭檔。
搭檔小結:
1、這次項目,是我們第一次一起合作完成的項目,通過結對編程的編程方法,我們十分有效率地完成了項目需求,途中出現了不少分歧點,但通過結對編程,我們都能及時地進行討論和統一觀點,使時間沒有過多地浪費在不必要的地方。
2、原本想讓用戶在程序裏面也能填寫答案,但由於想不到比較好的交互方式,所以最後沒有實現這個功能,只讓用戶在txt文件上手動填寫答案。
3、最後,和大佬一起做項目真的很舒服!!
七、參考來源
[1] 波蘭式、逆波蘭式與表達式求值