OO Unit1 總結
綜述
第一單元的任務主要是進行包含冪運算的表示式化簡,在隨後幾次作業的迭代開發後可以實現支援冪函式,括號巢狀,有限個自定義函式(及其巢狀),三角函式與求和函式。主體架構採用遞迴下降演算法拆分表示式為Expr(表示式)、Term(項)、Factor(因子),後對於拆分好的字串進行計算、化簡與合併同類項。
一、程式架構分析
HW1
-
實現一個包含加、減、乘與乘方的表示式展開與化簡,允許出現至多一層括號
-
程式碼架構
-
基本類及其行為
-
其中,可以分為遞迴下降部分、儲存因子及計算兩部分。
Lexer與Parser為遞迴下降法先拆分後按類讀取字串的過程,對錶達式進行初步的解析。通過遞迴下降演算法,將表示式分為Expr,Term與Variable,其內部邏輯與指導書中形式化表示貼合。
其中Expr表示:空白項 [加減 空白項] 項 空白項 | 表示式 加減 空白項 項 空白項
Term→ [加減 空白項] 因子 | 項 空白項 * 空白項 因子
Variable→ 變數因子 | 常數因子 | 表示式因子。
其中,最為關鍵的基本項為Variable類,用以表示最基本同時涵蓋所有型別因子的情況:
a*x**b我為其設定了三個元素來充分的表示:Cofficient(常數),Power(冪次),Negitive(符號),在合併同類相前,式子應被化簡為n個variable相加減的形式。由此,我完成了作業一在架構方面的基本構思。
關於拆分後表示式的計算:
初步的考慮採用遞迴的方法將因子計算,返回上一層項中計算,最後返回表示式類,再合併同類項輸出結果。最後因為時間(和心態QWQ)的問題,沒有在架構中成功實現,而採用了將每個項中的元素暴力相乘,最後一併在expr中進行加減,過長的method與多層巢狀的ifelse結構,為無窮的bug製造了絕佳的機會…也預示了後續迭代的痛苦重構:(
-
複雜度分析
-
(非常驚人的資料,由於在Main中做了大量字串的預處理,當做一次教訓了
-
化簡與合併同類項
-
儲存的容器十分關鍵,用Hashmap儲存Variable,key為power,value為Cofficient,方便計算與合併
-
一些細節的優化如:cofficient = 1、power = 0的情況,與
x**2->x*x;
-
HW2
-
內容簡述:在HW1的基礎上增加了三角函式、求和函式與自定義函式,多餘括號仍為最多一層。
對於求和函式與自定義函式,我採用不失語義的帶入法(通俗而言就是帶括號的帶入加細節處理),對錶達式進行預處理,轉化為符合HW1形式化表述的的表示式後,套用第一次的計算與化簡。
-
程式碼架構分析
在基本類中增加了三角函式類與自定義函式類,對於新增項各自放到類中處理完後,以表示式的形式新增到原有表示式中,同樣參與計算即可。此時,計算部分的巨大mothed還沒有重構(由於懶惰hhh),好在各種細節注意到了,沒有在互測環節傷痕累累。
-
複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getCos() | 8 | 5 | 3 | 6 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.getPower() | 3 | 1 | 7 | 7 |
Lexer.getSin() | 8 | 5 | 3 | 6 |
Lexer.next() | 13 | 2 | 8 | 9 |
Lexer.peek() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 4 | 4 |
Parser.parseFactor() | 25 | 9 | 14 | 14 |
Parser.parseTerm() | 2 | 1 | 3 | 3 |
advance.preCheck() | 8 | 1 | 8 | 9 |
advance.setStr(String) | 0 | 1 | 1 | 1 |
cal.cal1() | 27 | 1 | 8 | 8 |
cal.setExpr(Expr) | 0 | 1 | 1 | 1 |
expr.Cos.Cos(String) | 0 | 1 | 1 | 1 |
expr.Expr.Expr() | 0 | 1 | 1 | 1 |
expr.Expr.addTerm(Term) | 0 | 1 | 1 | 1 |
expr.Expr.cal(Term) | 91 | 1 | 19 | 19 |
expr.Expr.getMi() | 0 | 1 | 1 | 1 |
expr.Expr.getTerm() | 0 | 1 | 1 | 1 |
expr.Expr.getTerms() | 0 | 1 | 1 | 1 |
expr.Expr.setMi(int) | 0 | 1 | 1 | 1 |
expr.Expr.toString() | 3 | 1 | 3 | 3 |
expr.Function.getExpr() | 0 | 1 | 1 | 1 |
expr.Function.getItem() | 0 | 1 | 1 | 1 |
expr.Function.getName() | 0 | 1 | 1 | 1 |
expr.Function.setExpr(String) | 0 | 1 | 1 | 1 |
expr.Function.setName(char) | 0 | 1 | 1 | 1 |
expr.Function.setVariable(String) | 4 | 3 | 2 | 3 |
expr.Number.Number(BigInteger) | 0 | 1 | 1 | 1 |
expr.Number.toString() | 0 | 1 | 1 | 1 |
expr.Sin.Sin(String) | 0 | 1 | 1 | 1 |
expr.Term.Term() | 0 | 1 | 1 | 1 |
expr.Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
expr.Term.addNegitive(boolean) | 0 | 1 | 1 | 1 |
expr.Term.caculate() | 11 | 2 | 6 | 6 |
expr.Term.getFactors() | 0 | 1 | 1 | 1 |
expr.Term.getNegitive() | 0 | 1 | 1 | 1 |
expr.Term.toString() | 3 | 1 | 3 | 3 |
expr.Variable.Variable(BigInteger, int) | 0 | 1 | 1 | 1 |
expr.Variable.addCofficient(BigInteger) | 0 | 1 | 1 | 1 |
expr.Variable.addCos(String, int) | 7 | 3 | 3 | 6 |
expr.Variable.addNegitive1(boolean) | 0 | 1 | 1 | 1 |
expr.Variable.addSin(String, int) | 7 | 3 | 3 | 6 |
expr.Variable.copyCos(HashMap<String, Integer>) | 0 | 1 | 1 | 1 |
expr.Variable.copySin(HashMap<String, Integer>) | 0 | 1 | 1 | 1 |
expr.Variable.getCoefficient() | 0 | 1 | 1 | 1 |
expr.Variable.getCos() | 0 | 1 | 1 | 1 |
expr.Variable.getNegitive1() | 0 | 1 | 1 | 1 |
expr.Variable.getPower() | 0 | 1 | 1 | 1 |
expr.Variable.getSin() | 0 | 1 | 1 | 1 |
expr.Variable.toString() | 24 | 2 | 11 | 12 |
複雜度重災區仍然是計算cal部分,沒有和整體架構融為一體的計算模組雖然完成了相關任務,但是卻非常不利於迭代開發。
-
化簡與合併同列項
由於表示式最終仍被化簡為
Variable+Variable+的形式,故新新增的因子同樣應該以一定的形式儲存在Variable中
對於Sin與Cos,採用
Hashmap<String,Integer>
以三角函式內expr為key,三角函式的power為value,方便合併同類項
HW3
-
內容分析
在作業二的基礎上增添多層括號巢狀與自定義函式巢狀
-
程式碼架構
與第二次的UML圖幾乎完全相同,計算的環節完全重構,採用字尾表示式的方式來解決括號巢狀的問題,而對於表示式巢狀,採用遞迴帶入的方式處理,總體由第二次作業迭代開發完成。
-
複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Func.Func(HashMap<Integer, Function>, String) | 0 | 1 | 1 | 1 |
Func.checkFunc() | 155 | 18 | 29 | 31 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getCos() | 8 | 5 | 3 | 6 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.getPower() | 3 | 1 | 7 | 7 |
Lexer.getSin() | 8 | 5 | 3 | 6 |
Lexer.next() | 13 | 2 | 8 | 9 |
Lexer.peek() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.parseExpr() | 5 | 1 | 4 | 4 |
Parser.parseFactor() | 27 | 9 | 15 | 15 |
Parser.parseTerm() | 1 | 1 | 2 | 2 |
advance.preCheck() | 8 | 1 | 8 | 9 |
advance.setStr(String) | 0 | 1 | 1 | 1 |
caculate.cal() | 23 | 4 | 16 | 16 |
caculate.getCos() | 8 | 5 | 3 | 6 |
caculate.getNumber() | 2 | 1 | 3 | 3 |
caculate.getSin() | 8 | 5 | 3 | 6 |
caculate.muLit(Expr, Expr) | 21 | 1 | 8 | 8 |
caculate.setExpr(String) | 0 | 1 | 1 | 1 |
sinCos.simPlify() | 18 | 7 | 6 | 9 |
sinCos.sinCos(String) | 0 | 1 | 1 | 1 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.Cos.Cos(String) | 0 | 1 | 1 | 1 |
expr.Expr.Expr() | 0 | 1 | 1 | 1 |
expr.Expr.addStandExpr(Variable) | 0 | 1 | 1 | 1 |
expr.Expr.addTerm(Term) | 0 | 1 | 1 | 1 |
expr.Expr.copyExpr(Expr) | 0 | 1 | 1 | 1 |
expr.Expr.fan() | 4 | 1 | 3 | 3 |
expr.Expr.getMi() | 0 | 1 | 1 | 1 |
expr.Expr.getStandExpr() | 0 | 1 | 1 | 1 |
expr.Expr.getTerm() | 0 | 1 | 1 | 1 |
expr.Expr.getTerms() | 0 | 1 | 1 | 1 |
expr.Expr.setMi(int) | 0 | 1 | 1 | 1 |
expr.Expr.toString() | 11 | 1 | 6 | 6 |
expr.Function.getExpr() | 0 | 1 | 1 | 1 |
expr.Function.getItem() | 0 | 1 | 1 | 1 |
expr.Function.getName() | 0 | 1 | 1 | 1 |
expr.Function.setExpr(String) | 0 | 1 | 1 | 1 |
expr.Function.setName(char) | 0 | 1 | 1 | 1 |
expr.Function.setVariable(String) | 4 | 3 | 2 | 3 |
expr.Number.Number(BigInteger) | 0 | 1 | 1 | 1 |
expr.Number.toString() | 0 | 1 | 1 | 1 |
expr.Sum.SumString() | 7 | 1 | 5 | 5 |
expr.Sum.setBegin(BigInteger) | 0 | 1 | 1 | 1 |
expr.Sum.setSum(String) | 0 | 1 | 1 | 1 |
expr.Term.Term() | 0 | 1 | 1 | 1 |
expr.Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
expr.Term.addNegitive(boolean) | 0 | 1 | 1 | 1 |
expr.Term.caculate() | 11 | 2 | 6 | 6 |
expr.Term.getFactors() | 0 | 1 | 1 | 1 |
expr.Term.getNegitive() | 0 | 1 | 1 | 1 |
expr.Term.toString() | 3 | 1 | 3 | 3 |
expr.Variable.Variable(BigInteger, int) | 0 | 1 | 1 | 1 |
expr.Variable.addCofficient(BigInteger) | 0 | 1 | 1 | 1 |
expr.Variable.addCos(String, int) | 7 | 3 | 3 | 6 |
expr.Variable.addNegitive1(boolean) | 0 | 1 | 1 | 1 |
expr.Variable.addSin(String, int) | 7 | 3 | 3 | 6 |
expr.Variable.copyCos(HashMap<String, Integer>) | 0 | 1 | 1 | 1 |
expr.Variable.copySin(HashMap<String, Integer>) | 0 | 1 | 1 | 1 |
expr.Variable.getCoefficient() | 0 | 1 | 1 | 1 |
expr.Variable.getCos() | 0 | 1 | 1 | 1 |
expr.Variable.getNegitive1() | 0 | 1 | 1 | 1 |
expr.Variable.getPower() | 0 | 1 | 1 | 1 |
expr.Variable.getSin() | 0 | 1 | 1 | 1 |
expr.Variable.setMultiItem(boolean) | 0 | 1 | 1 | 1 |
expr.Variable.toString() | 35 | 2 | 14 | 15 |
二、bug分析
第一次作業:
計算過程過於暴力以至於式子太長時出現多種情況的處理不好,一直在修改cal內部的邏輯。
第二次作業:
強測錯了四個點,互測時被hack了一刀,一共查出了三個bug:
-
符號的問題,在expr中toString中多設定了符號的判斷,導致錯誤。
-
第二個是類似於x*-1的問題
-
第三個是式子結果為0的情況,應判斷空串情況再輸出。
第三次作業:
強測錯了四個點,互測時被hack了sum的範圍,用的long型別,找到了同房間小夥伴的一些bug,主要有以下:
-
大數bug,互測的同學都在爆int爆long(互相傷害),使用了int或long儲存資料,而非BigInteger造成了bug。
-
sum函式的bug:出現s1n,s2n的情況;上界小於下界的情況;BigInteger負數的情況;sum巢狀的情況…
-
零次冪bug,這部分bug第一次作業應該就修過了,但是遇到sin和cos函式,自定義和sum函式引數替換,以及層層巢狀關係後,有時零次冪又會輸出奇怪的結果。
三、Hack策略分析
我一般只是手動輸入一些資料,比如x**0、x*-2*-1、1**+0001
等等一些簡單易錯的資料,還有一些邊界大數的資料,充分利用程式碼架構中最冗雜邏輯性較差的部分找出來漏洞去hack,用自己的bug樣例…在對自己的程式做了較多測試,積累了一定bug的經驗之後,對於相似架構的程式碼可以嘗試同樣的方法hac。
四、收穫總結
在第一單元的三次作業中,我逐漸學習到了面向物件程式設計的思想和方法,深刻體會到到了一個良好的架構對程式迭代開發的重要性,設計是重中之重,在開發之初就應該保有著為迭代做準備的想法,同時代碼風格檢查也讓我改正了很多不好的習慣。以下是我在第一單元的主要收穫:
-
在著手開始寫之前,把基本的程式碼架構想好,不可以邊寫邊重構:( 把各個類、函式的關係搞清楚.
-
仔細閱讀指導書,深入理解形式化表述的深層邏輯,思考程式設計的目的,與迭代的可能方向,在指導書中有著出題者的意圖與做法的而推薦,都應該仔細理解消化。
-
重構要趁早,趕到第三次作業再去面對一個迭代效能幾乎為0的程式碼內心實際是崩潰的,如果沒有辦法再一開始就避免無法迭代需要重構的情況,就在發現問題後立馬著手去做,對於壞的架構瘋狂豐富細節的意義不是很大
五、心得體會
因為寒假的Pre沒有認真做好,前三週經歷了一個邊學基礎知識邊處理問題的痛苦經歷,思路的匱乏和無限的重構,耗費了很多的時間,但是收穫也很多,可以說是一個被逼著快速入門的過程了QWQ 另外,我充分感覺到了面向物件這門課對個人程式碼能力的要求,作業發下來後對著題目坐牢一天,才能開始著手實現,有時需要和同學討論,求教助教等等,實現前的壓力和實現後的成就感反覆疊加…第二單元增加對於架構與迭代的考慮,避免走第一單元過度重構的彎路。