這個作業屬於哪個課程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
---|---|
這個作業要求在哪裡 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230 |
這個作業的目標 | <運用C++實現四則運演算法則的命令列程式> |
團隊成員1 | 李梓灝3122004695 |
團隊成員2 | 吳燦豪3122004710 |
一.Github地址
https://github.com/Memset-Lee/Memset-Lee/tree/main/3122004695-PartnerProject
二.程式需求
1.題目:實現一個自動生成小學四則運算題目的命令列程式(也可以用影像介面,具有相似功能)。
2.說明:
自然數:0, 1, 2, …。
真分數:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
運算子:+, −, ×, ÷。
括號:(, )。
等號:=。
分隔符:空格(用於四則運算子和等號前後)。
算術表示式:
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2為表示式,n為自然數或真分數。
四則運算題目:e = ,其中e為算術表示式。
3.需求:
使用 -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
四則運算題目2
……
其中真分數在輸入輸出時採用如下格式,真分數五分之三表示為3/5,真分數二又八分之三表示為2’3/8。
在生成題目的同時,計算出所有題目的答案,並存入執行程式的當前目錄下的Answers.txt檔案,格式如下:
答案1
答案2
特別的,真分數的運算如下例所示:1/6 + 1/8 = 7/24。
程式應能支援一萬道題目的生成。
程式支援對給定的題目檔案和答案檔案,判定答案中的對錯並進行數量統計,輸入引數如下:
Myapp.exe -e .txt -a .txt
統計結果輸出到檔案Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”後面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。為簡單起見,假設輸入的題目都是按照順序編號的符合規範的題目。
三.PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 60 |
Estimate | 估計這個任務需要多少時間 | 30 | 30 |
Development | 開發 | 240 | 300 |
Analysis | 需求分析(包括學習新技術 | 60 | 30 |
Design Review | 生成設計文件 | 120 | 60 |
Coding Standard | 設計複審(為目前的開發制定合適的規範) | 30 | 30 |
Design | 具體設計 | 60 | 60 |
Coding | 具體編碼 | 180 | 210 |
Code Review | 程式碼複審 | 30 | 60 |
Test | 測試(自我測試,修改程式碼,提交修改) | 90 | 90 |
Reporting | 報告 | 120 | 60 |
Test Repor | 測試報告 | 60 | 30 |
Size Measurement | 計算工作量 | 30 | 30 |
Postmortern & Process Improvement Plan | 事後總結,並提出過程改進計劃 | 30 | 30 |
合計 | 970 | 1050 |
四.程式設計分佈
程式的宏定義和全域性變數:
點選檢視程式碼
#define ll long long
#define llf LLONG_MAX
using namespace std;
ll questionNum = 100, maxRange = 100;//初始題目數量,初始自然數範圍
ll parenthesesProbability = 5, denominatorRange = 100;//括號機率,分母範圍
string exerciseFile, answerFile;//題目檔案,答案檔案
vector<string>allSymbol = { "+","-","*","/" };//運算子
static mt19937_64 randomNumberGenerator(chrono::steady_clock::now().time_since_epoch().count());//隨機數生成器
uniform_int_distribution<ll>symbolNumRange(1, 3);//符號數量
uniform_int_distribution<ll>symbolRange(0, 3);//運算子種類
uniform_int_distribution<ll>parentheses(0, 100);//括號
程式由12個子函式組成,分別為:
1. ll gcd(ll a, ll b) //求最大公因數
2. struct Number //自然結構體
3. string addParentheses(string s)//隨機新增括號
4. Number getNum(string s, ll idx)//獲取表示式中的自然數
5. string getString(Number x)//將自然數化為正確形式
6. string getSimpleAns(string s)//計算無括號式子答案
7. bool checkParentheses(string s)//查詢括號
8. string getAns(string s)//計算答案
9. void generateQuestion()//生成問題
10. string getTrue(string s)//去除標號
11. void outputCheckAns(vectorcorrect, vectorwrong)//輸出判斷對錯並進行數量統計的結果
12. void checkAns()//判斷答案對錯並進行數量統計
13. int main(int argc, char* argv[])
主函式的流程圖為:
以上函式的關係:
1. 主函式 (main)
- 負責程式的入口,處理命令列引數,決定接下來的操作。
2. 引數處理
- 檢查命令列引數的數量和有效性。
- 提取引數用於後續處理。
3. 條件判斷
- 根據提取的引數判斷執行的操作:
- generateQuestion(): 當引數為 -n 和 -r 時被呼叫。
- 負責生成問題,使用 questionNum 和 maxRange。
- checkAns(): 當引數為 -e 和 -a 時被呼叫。
- 負責檢查答案,使用 exerciseFile 和 answerFile。
- generateQuestion(): 當引數為 -n 和 -r 時被呼叫。
4. 錯誤處理
- 當引數數量不正確或選項無效時,會輸出錯誤資訊並呼叫 printUsage()。
- printUsage() 函式用於顯示程式的使用說明,幫助使用者理解如何正確輸入引數。
5. 輸出
- 在成功完成任務之後,都會輸出 "Finish" 表示處理完畢。
五. 效能分析
在VS自帶的效能分析軟體中,我們可得到以下結果:
各函式詳細所需時間:
同時我們也可以得到該程式的CPU使用率分佈圖:
由此我們可以得出,佔用相關cpu以及耗費時間較長的程式其實大部分都是呼叫庫函式,其餘部分執行時間不算太多。但我們對自然數結構體進行了相對應的改進,降低程式出BUG的機率,同時也對部分函式進行改進,降低程式的時間複雜度。
對Number結構體的改進:
點選檢視程式碼
#include <iostream>
#include <numeric> // for std::gcd
#include <cstdlib>
using namespace std;
struct Number // 自然數結構體
{
ll numerator, denominator; // numerator:分子,denominator:分母
// 建構函式
Number(ll num = 0, ll den = 1) : numerator(num), denominator(den) {
if (denominator == 0) {
throw invalid_argument("Denominator cannot be zero");
}
simplify();
}
// 簡化分數
void simplify() {
ll gcd_value = gcd(abs(numerator), abs(denominator));
numerator /= gcd_value;
denominator /= gcd_value;
// 如果分母為負數,調整符號
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
}
Number operator + (const Number& x) const {
ll temp1 = numerator * x.denominator + x.numerator * denominator;
ll temp2 = denominator * x.denominator;
return {temp1, temp2}; // 返回未簡化的分數
}
Number operator - (const Number& x) const {
ll temp1 = numerator * x.denominator - x.numerator * denominator;
ll temp2 = denominator * x.denominator;
return {temp1, temp2}; // 返回未簡化的分數
}
Number operator * (const Number& x) const {
ll temp1 = numerator * x.numerator;
ll temp2 = denominator * x.denominator;
return {temp1, temp2}; // 返回未簡化的分數
}
Number operator / (const Number& x) const {
if (x.numerator == 0) {
throw invalid_argument("Cannot divide by zero");
}
ll temp1 = numerator * x.denominator;
ll temp2 = denominator * x.numerator;
return {temp1, temp2}; // 返回未簡化的分數
}
// 輸出過載
friend ostream& operator<<(ostream& os, const Number& n) {
os << n.numerator << "/" << n.denominator;
return os;
}
};
六. 程式碼說明
重要函式程式碼:struct Number //自然數結構體
程式碼分析
1. 結構體定義:
- Number結構體包含兩個成員:numerator(分子)和denominator(分母)。
2. 運算子過載:
- 每個運算子過載方法都按照分數的運算規則來計算結果。
- 在每個運算結束後,使用 gcd 函式來約分結果。
3. 約分過程:
- 使用 gcd 函式計算分子和分母的最大公約數,並進行簡化。
點選檢視程式碼
struct Number//自然數結構體
{
ll numerator, denominator;//numerator:分子,denominator:分母
Number operator + (const Number& x) const
{
ll temp1 = numerator * x.denominator + x.numerator * denominator;
ll temp2 = denominator * x.denominator;
return { temp1 / gcd(abs(temp1),abs(temp2)),temp2 / gcd(abs(temp1),abs(temp2)) };
}
Number operator - (const Number& x) const
{
ll temp1 = numerator * x.denominator - x.numerator * denominator;
ll temp2 = denominator * x.denominator;
return { temp1 / gcd(abs(temp1),abs(temp2)),temp2 / gcd(abs(temp1),abs(temp2)) };
}
Number operator * (const Number& x) const
{
ll temp1 = numerator * x.numerator;
ll temp2 = denominator * x.denominator;
return { temp1 / gcd(abs(temp1),abs(temp2)),temp2 / gcd(abs(temp1),abs(temp2)) };
}
Number operator / (const Number& x) const
{
ll temp1 = numerator * x.denominator;
ll temp2 = denominator * x.numerator;
return { temp1 / gcd(abs(temp1),abs(temp2)),temp2 / gcd(abs(temp1),abs(temp2)) };
}
};
重要函式程式碼:string addParentheses(string s)//隨機新增括號
程式碼分析
1. 計數運算子:
- 使用 cnt1 和 cnt2 分別計算加法/減法(+ 和 -)和乘法/除法(* 和 /)運算子的數量。
2. 查詢最後一個加法或減法運算子:
- 透過 idx 記錄最後一次出現的加法或減法運算子的位置。
3. 查詢空格:
- 使用 idx1 和 idx2 找到最近的空格,以便確定括號應該放置的位置。
4. 新增括號:
- 根據找到的位置,構建新的字串,將括號新增在相應的位置。
5. 返回結果:
- 如果沒有條件滿足,則返回原始字串。
點選檢視程式碼
string addParentheses(string s)//隨機新增括號
{
string temp = "";
ll i, tempidx, cnt1 = 0, cnt2 = 0, idx = -1, idx1 = -1, idx2 = -1;
for (i = 0; i < (ll)s.size(); i++)
{
if (s[i] == '+' || s[i] == '-') cnt1++, idx = i;
else if (s[i] == '*' || (s[i] == '/' && s[i - 1] == ' ' && s[i + 1] == ' ')) cnt2++;
}
if (cnt1 != 0 && cnt2 != 0 && parentheses(randomNumberGenerator) % parenthesesProbability == 0)
{
tempidx = idx - 2;
while (tempidx >= 0)
{
if (s[tempidx] == ' ')
{
idx1 = tempidx;
break;
}
tempidx--;
}
tempidx = idx + 2;
while (tempidx < (ll)s.size())
{
if (s[tempidx] == ' ')
{
idx2 = tempidx;
break;
}
tempidx++;
}
if (idx1 == -1)
{
temp += "(";
for (i = 0; i < idx2; i++) temp += s[i];
temp += ")";
for (; i < (ll)s.size(); i++) temp += s[i];
}
else if (idx2 == -1)
{
for (i = 0; i <= idx1; i++) temp += s[i];
temp += "(";
for (; i < (ll)s.size(); i++) temp += s[i];
temp += ")";
}
else if (idx1 != -1 && idx2 != -1)
{
for (i = 0; i <= idx1; i++) temp += s[i];
temp += "(";
for (; i < idx2; i++) temp += s[i];
temp += ")";
for (; i < (ll)s.size(); i++) temp += s[i];
}
return temp;
}
else
{
return s;
}
}
七. 測試執行
可執行程式在命令列輸入必要引數並執行,所得到的結果如下圖所示:
以上是其中一次測試,我們還對程式進行了二十次以上的不同資料的測試,發現程式給出的答案與我們手動計算的答案相同,所以我們經過多次驗證後確定程式沒有問題。
八. 專案小結
1.感受
在本次組隊專案中,我們考慮到了兩個人擅長領域的不同,所以我們經過商量以及計劃對本次專案進行分工合作,讓工作效率更快更好,也能讓雙方都得到較為不錯的體驗。
2. 收穫
在這次合作中,我們成功完成了專案目標,具體包括提高了工作效率和達成了預定的成果。這次經歷讓我在溝通和時間管理上有了顯著提升,而對方在問題解決方面表現出色。我們透過分享經驗,互相學習,極大豐富了彼此的視野。同時,這種緊密的團隊協作讓我們在面對挑戰時能夠迅速找到解決方案。展望未來,我希望能繼續與對方合作,共同迎接新的挑戰,實現更大的目標。