本次做業是實現一個面向小學教師的四則運算題目生成程序,它的功能包括生成題目,以及根據題目計算結果並自動給同窗的做業打分。算法
從表格中能夠看出,用於設計和測試的時間較多。express
PSP2.1 | Personal Software Process Stages | Time |
---|---|---|
Planning | 計劃 | |
· Estimate | · 估計這個任務須要多少時間 | 20h |
Development | 開發 | |
· Analysis | · 需求分析 (包括學習新技術) | 1h |
· Design Spec | · 生成設計文檔 | 1h |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 1h |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 0.5h |
· Design | · 具體設計 | 2h |
· Coding | · 具體編碼 | 3h |
· Code Review | · 代碼複審 | 1h |
· Test | · 測試(自我測試,修改代碼,提交修改) | 2h |
Reporting | 報告 | |
· Test Report | · 測試報告 | 3h |
· Size Measurement | · 計算工做量 | 0.5h |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 1h |
合計 | 16h |
根據老師的要求,所寫程序功能大致分爲兩部分:編程
其中,第二個功能較爲簡單,就是一個經典的中綴表達式求值的問題,咱們能夠用兩個棧來隨便維護一下就好。數組
第一個功能中要求較多:數據結構
不一樣
的。不一樣
定義爲:less
任何兩道題目不能經過有限次交換+和×左右的算術表達式變換爲同一道題目數據結構和算法
首先分析第一個功能。函數
第一個要求很容易知足,經過控制一些參數便可實現。性能
第二個要求則須要對錶達式進行運算,若是出現了負數,則交換減號兩邊的表達式。(或採起其餘方式避免)學習
第三個要求至關於對題目的簡化。僅用考慮不超過三個運算符的表達式
第四個要求則須要咱們同時處理分數和整數。咱們考慮使用一個統一的Factor類來實現。
第五個要求是這些要求中較難的一個。它要求咱們生成的表達式不能有相同的計算順序。結合表達式的性質,咱們很容易就想到了樹的最小表示法
。
即用二叉樹的形式存放表達式,那麼只要兩棵樹的最小表示不一樣,則這兩個表達式必定是不一樣的。
而且因爲表達式中的運算符數量較少,將樹最小表示的過程能夠暴力的來作。
ps:剛開始沒有限制表達式的數量,那麼若是要對錶達式樹進行最小表示,則須要樹的每個節點均爲子表達式,而且須要對錶達式類重載小於運算符,實現起來較爲繁瑣。後來老師加上了對運算符的限制,則大大下降了編程複雜度。
類定義:
class Factor { int fraction; long long numerator, denominator; } class Expression { int opNum; Factor fac[FACTOR_SIZE]; char op[FACTOR_SIZE]; }
上述代碼描述了本程序中關鍵的兩個類:factor和Expression。
Factor類實現了分數和整數的統一處理(包括四則運算、輸出等),其numerator屬性爲分子,denomination屬性爲分母,fraction屬性標記了其是否爲分數。
Expression類實現了表達式的處理,包括表達式的最小表示,處理出現負數和除數爲零的狀況等不合法狀況以及輸出。
有了上述基礎,表達式的生成就變得很是簡單了。
至此,第一個功能就初步設計完畢了。
第二個功能則沒什麼好說的,就是讀入兩個文件,將兩個文件中保存的答案記錄下來並進行計算和比較,從而得出結果。須要注意的就是一些文件讀取的問題和文件內容不合法的判斷。
對於Factor和Expression類,我最初在每一個類中都定義了一個print()方法,用於輸出該類型的變量。然而以後發現因爲須要生成多個表達式,且每一個表達式生成後都要向兩個文件中寫入數據,那麼採用輸出重定向的方式就較爲不便。因而我將print()方法改成了與Java中相似的toString()方法,該方法會返回要輸出的字符串。這樣就能夠方便的使用文件流的方式來存取數據了。
對於將表達式進行最小表示後的判重問題,我剛開始的思路是開一個不少維的map來存表達式的全部運算符和數值。大概相似這個樣子:
map<Factor, map<Factor, map<Factor ,map<char, map <char, int> > > > > mp;
寫完以後發現編譯器給出了警告:
warning C4503: 'std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::erase' : decorated name length exceeded, name was truncated
查閱資料後發現,這是因爲模板展開後的標識符長度超過了編譯器限制。
那如何消除這個警告呢?後來我纔想到,既然要求表達式惟一,那麼直接將最小表示後的表達式字符串做爲key就行了嘛……
因此僅須要一個set<string>
來存放不一樣的表達式便可。
對於輸入的參數中要求生成較多數量的表達式可是給出的表達式中數值得限制較小,就可能會出現湊不出要求的表達式數量的狀況。對於這種狀況,目前尚未想到完美的方法來保證全部可能的表達式均被生成。僅限定了一個閾值,若是在循環了這麼屢次後仍然沒有找到一個之前沒有出現的表達式,那麼就近似地認爲沒有更多的表達式了。
不過在實際應用中,應該不會出現這種極限的狀況。
在代碼寫完後,我進行了Code Review和debug。
Code Review中,發現了兩處寫錯的地方,debug中,發現了三個細節問題(判斷除0錯誤時的邏輯問題、生成數值的時候fraction取值的問題以及文件讀寫的問題),並進行了改正。
在後續的測試中,發現了即便數值限制爲100,中間結果也有可能會超過int的範圍,所以必須採用long long類型的變量來存儲數值。
測試用例以下:
ExpressionGeneration.exe -r 10000 -n 1 pause # generate Exercises.txt including a expression which values are less than 10000 # Answers.txt including the expression's answer
ExpressionGeneration.exe -n 5 pause # input error,display the help message`
ExpressionGeneration.exe -n 5 pause # input error,display the help message`
ExpressionGeneration.exe -n 5.1 -r 4 pause # input invalid, display the help message
ExpressionGeneration.exe -n -r 4 5 pause # input invalid, display the help message
ExpressionGeneration.exe -n 10000 -r 3 pause # generate Exercises.txt including 10000 expressions which values are less than 3 # Answers.txt including the answers of all the expressions
ExpressionGeneration.exe -n 10000 -r 2 pause # It doesn't have so many expressions
測試答案的測試用例因爲文件較大,在此再也不展現,圍繞文件讀取合法性、題目格式的正確性、答案格式的正確性、題目文件和答案文件是否匹配、答案正確的題目數量等方面設計測試用例便可。
不過總的來講,運行效率能夠接受。toString()方法也沒有太大的改進空間了。
完成此次我的項目的過程當中,我進一步體會到作工程與解決數學問題的區別。
在實際項目中,需求每每不是可以僅從題目要求中讀出來的,更多的仍是要結合實際狀況進行分析。同時,作項目的目的是爲了服務生活,有時,咱們能夠根據生活中的實際狀況來對問題進行簡化。(好比此次項目中對運算符個數的限制),而不是一味地構想難解的問題。
此外,我也發現了數據結構和算法基礎在項目實現中的重要性。在本次項目中,若是可以想到平時常常用到的判斷樹同構問題的最小表示法的話,那麼這就是一個很是熟悉且優美的問題了。(不過個人代碼實現仍是不夠優美……)