BUAA OO 第一單元總結
一、第一次作業
(1)類結構設計
第一次題目架構比較簡單,具體結構設計如下(圖1):
- 基本資料型別:
-
Polynomial
:用Hashmap<int(指數), BigInteger(係數)>
將表示式/項/因子資料內容統一形式- 運算方法:加法
add
、乘法mul
、乘方pow
- 格式化輸出:
toString
方法將其內容輸出(含正項提前處理),addPowerFunc
負責將每個項轉化為字串形式。
- 運算方法:加法
-
- 表示式解析:
-
Lexer
:使用正則表示式(?<num>(?<=[^\\dx)]|^)[+-]?\\d+|x)|(?<opt>\\*\\*|[)(+\\-*])
{"-","-","-2","*","(","x",")"}
的字串序列 -
parser
:使用遞迴下降法對得到的字串序列進行解析,得到表示式/項/因子例項物件。
-
- 表示式儲存
-
Expr
:表示式- 運算方法:
update
將terms
中的Polynomial
相乘後,在乘power
次冪並記錄到res
中;negate
將res
取負。 - 更新方法:
addTerm
增加新的項 - 輸出方法:
toString
呼叫res.toString
- 運算方法:
-
Term
:項- 運算方法:
update
將factors
中的Polynomial
相加並記錄到res
中,negate
將res
取負。 - 更新方法:
addFactor
- 運算方法:
-
Factor
(介面):包含Expr
和ValueFactor
- 運算方法:
negate
將res
取負 - 取值方法:
getRes
獲得Factor
中資料值 - 介面實現類:
-
Expr
:表示式 -
ValueFactor
:常數(冪次為0)以及冪函式
-
- 運算方法:
-
(2)度量分析
從圖中可以看出Polynomial
和Parser
類OCavg較高,高複雜度在於遞迴呼叫(Parser
)和雙重迴圈(Polynomial
)。
(3)Hack情況
- 己方bug分析:本次作業個人在強測和弱側中均未出現bug
- 對方bug分析:
- 解析
- -
時出錯
- 解析
二、第二次作業
(1)類結構設計
第二次作業增加了三角函式、自定義函式、求和函式,程式碼量相較於第一次有明顯增長,不過整體架構上沒有太大變化。值得一提的是在處理自定義函式/求和函式時,個人的處理方法是先把原表示式以字串形式儲存下來再用類似Parser的方法解析,由於這樣Function和Parser的方法有相似之處(也為了後續巢狀表示式處理),因此將Parser和Function進行了合併(由於耦合過高,第三次作業將Function類進行了解耦)
- 基本資料型別:
-
Polynomial
:用Hashmap<Hashmap<Factor, int(指數)>, BigInteger(係數)>
將表示式/項/因子資料內容統一形式。其中Factor
有且僅有三角函式、冪函式、常數。****- 運算方法:加法
add
、乘法mul
、乘方pow
- 格式化輸出:
toString
方法將其內容輸出(含正項提前處理),addPowerFunc
負責將每個項轉化為字串形式。 - 判斷相等方法:
hashCode
和equal
- 化簡方法:
-
replaceSinX2/replaceCosX2
:負責形如 \(asin(x)^2+bcos(x)^2+c\) 平方和(其中\(a\)、\(b\)、\(c\)、\(x\))均為任意因子/項。 -
replaceSinX4
:負責形如 \(a(sin(x)^4-cos(x)^4)\)的化簡。 -
buildSinXnPowers/buildCosXnPowers/buildConstXnPowers
:在化簡過程中構造含 \(sin(x)^n\) / \(cos(x)^n\) / 去掉對應三角函式的項
-
- 運算方法:加法
-
- 表示式解析:
-
Lexer
:使用正則表示式((?<=[^\w)]|^)[+-]?\d+)|(\*\*|[)(+\-*])|(sin|cos|sum)|([a-z,])
匹配每個有效字串,得到形如{"-","-","-2","*","(","x",")"}
的字串序列(New!) -
Function
:- 解析方法:
-
parseXXX
:使用遞迴下降法對得到的字串序列進行解析,得到表示式/項/因子例項物件或者對冪次解析。 -
accessSign
:對錶達式/項首項符號的解析
-
- 計算方法:
-
calculate
:呼叫後傳入引數,得到自定義函式的值(Expr
)。
-
- 解析方法:
-
- 表示式儲存
-
Expr
:表示式- 運算方法:
updatePower
將res
乘power
次冪並記錄到res
中;update
將terms
中的Polynomial
相乘後記錄到res
中;negate
將res
取負。 - 更新方法:
addTerm
增加新的項 - 輸出方法:
toString
呼叫res.toString
- 化簡方法:
simplyfy
將呼叫res.simplyfy
- 判斷相同方法:
hashCode
和equal
- 複製方法:
deeplClone
創造一個新的Expr
- 運算方法:
-
Term
:項- 運算方法:
update
將factors
中的Polynomial
相加並記錄到res
中,negate
將res
取負。 - 更新方法:
addFactor
增加新的因子 - 判斷相等方法:
hashCode
和equal
- 運算方法:
-
Factor
(介面):-
negate
:將res
取負 -
getRes
:獲得Factor
中資料值 -
deepClone
:深拷貝 -
setPower
:設定Factor
的冪次 - 介面實現類
-
Expr
:表示式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
Var
:冪函式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
Constant
:常數- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
TtrigonometricFactor
:三角函式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 子類:
-
Sin
:正弦函式 -
Cos
:餘弦函式
-
- 判斷相等方法:
-
-
-
(2)度量分析
從圖中可以看出Poly
類的OCavg和WMC較高,Function
類(原Parser
類)的WMC較高,原因同第一次作業。
(3)Hack情況
- 己方bug分析:本次作業個人在強測未出現bug,互測出現兩個bug
-
sin(0)**0
解析出錯:原因是如果檢查到sin(0)
時直接返回常數0,未考慮冪次 - 化簡含
cos(x)**2
的表示式時出錯:原因是構建時把含cos
的項誤寫成含sin
的項
-
- 對方bug分析:
-
sin(0)**0
解析出錯 -
sin(-1)**2
解析出錯(底數換成-sin(1)後沒有考慮冪次對最終符號的影響)
-
三、第三次作業
第三次作業允許三角函式/自定義函式的巢狀,相較於第二次作業幾乎沒有改動,僅僅對Poly類和Function類做了解耦,提取了部分方法,同時改變了一些方法/類名,增加可讀性。
(1)類結構設計
- 基本資料型別:
-
Polynomial
:用Hashmap<Hashmap<Factor, int(指數)>, BigInteger(係數)>
將表示式/項/因子資料內容統一形式。其中Factor
有且僅有三角函式、冪函式、常數。****- 運算方法:加法
add
、乘法mul
、乘方pow
- 格式化輸出:
toString
方法將其內容輸出(含正項提前處理),addPowerFunc
負責將每個項轉化為字串形式。 - 判斷相等方法:
hashCode
和equal
- 判斷資料型別方法:
equalX2
、equalConstant
、equalExpr
- 運算方法:加法
- PolySimplyfy:化簡方法類
-
handleSinX2/handleCosX2
:負責形如 \(asin(x)^2+bcos(x)^2+c\) 平方和(其中\(a\)、\(b\)、\(c\)、\(x\))均為任意因子/項。 -
handleSinX4
:負責形如 \(a(sin(x)^4-cos(x)^4)\)的化簡。 -
buildSinXnPowers/buildCosXnPowers
:在化簡過程中構造含 \(sin(x)^n\) / \(cos(x)^n\) / 去掉對應三角函式的項 -
replaceFactor
:替換項中的Factor
-
handleSinXCosX
:處理Sin
二倍角化簡 -
handleCos2X
:處理Cos
二倍角化簡
-
-
- 表示式解析:
-
Lexer
:使用正則表示式((?<=[^\w)]|^)[+-]?\d+)|(\*\*|[)(+\-*])|(sin|cos|sum)|([a-z,])
匹配每個有效字串,得到形如{"-","-","-2","*","(","x",")"}
的字串序列(New!) -
Parser
:- 解析方法:
-
parseXXX
:使用遞迴下降法對得到的字串序列進行解析,得到表示式/項/因子例項物件或者對冪次解析。 -
accessSign
:對錶達式/項首項符號的解析
-
- 解析方法:
-
Function
:- 計算方法:
-
calculate
:呼叫後傳入引數,得到自定義函式的值(Expr
)
-
- 計算方法:
-
- 表示式儲存
-
Expr
:表示式- 運算方法:
updatePower
將res
乘power
次冪並記錄到res
中;update
將terms
中的Polynomial
相乘後記錄到res
中;negate
將res
取負。 - 更新方法:
addTerm
增加新的項 - 輸出方法:
toString
呼叫res.toString
- 化簡方法:
simplyfy
將呼叫res.simplyfy
- 判斷相同方法:
hashCode
和equal
- 複製方法:
deeplClone
創造一個新的Expr
- 運算方法:
-
Term
:項- 運算方法:
update
將factors
中的Polynomial
相加並記錄到res
中,negate
將res
取負。 - 更新方法:
addFactor
增加新的因子 - 判斷相等方法:
hashCode
和equal
- 運算方法:
-
Factor
(介面):-
negate
:將res
取負 -
getRes
:獲得Factor
中資料值 -
deepClone
:深拷貝 -
setPower
:設定Factor
的冪次 - 介面實現類
-
Expr
:表示式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
Var
:冪函式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
Constant
:常數- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 判斷相等方法:
-
TtrigonometricFactor
:三角函式- 判斷相等方法:
hashCode
和equal
- 輸出方法:
toString
- 子類:
-
Sin
:正弦函式-
normalize
:標準化方法,將Sin中內容轉化為統一格式(改變正負)
-
-
Cos
:餘弦函式-
normalize
:標準化方法,將Cos中內容轉化為統一格式(改變正負)
-
-
- 判斷相等方法:
-
-
-
(2)度量分析
從圖中可以看出Poly
類和PolySimplyfy
的OCavg和WMC較高,Parser的WMC較高,Poly
和Parser
原因同第一次作業,PolySimplyfy
原因為每次化簡時需要遍歷所有鍵值對,同時一直化簡到無法化簡為止。
(3)Hack情況
本次強測結果比較慘痛,根本原因在於課下測試不充分。
- 己方bug分析:本次作業個人在強測和互測共出現3個bug
-
Poly
中equalX2
方法出錯:未考慮x^2
前係數 -
-sin(x)**0
解析出錯:未考慮三角函式本身負號 -
getContentPoly
方法複雜度過高,需要多次呼叫mul
等函式,導致三角函式巢狀過多是TLE
-
- 對方bug分析:
- 用
int
型別 解析求和函式的起始或結束常數 -
sin((-x*x))
化簡出錯 -
sum
中出現i**0
出錯
- 用
四、架構設計體驗
在第一次作業時,個人採取的方法為傳統的用棧解析表示式,結果中測沒過(後來發現是正則表示式的問題)。於是又用遞迴下降方法重新做了一遍,結果三次作業都沒有進行大規模重構。現在想想如果採取用棧解析表示式的方法會導致方法冗長,每加入新的運算子都要對多個函式進行改寫,不符合面向物件設計思想
在第二次作業時遇到最大的問題就是如何表示資料(多項式),在分析第一次架構的表示資料方法使用了Hashmap<Hashmap<Factor, int>, BigInteger>
對資料進行儲存。同時,完成基本程式碼編寫花費了近乎一天半的時間對程式碼進行小範圍重構(例如提取函式、分解函式、提取父類、重新命名等等)(這一過程中《重構:改善既有程式碼設計》一書對我啟發頗深)
在第三次作業時由於第二次作業的程式碼基本上已經能滿足需求,所以在簡單對部分類進行解耦後,花費了更多時間在提高效能上,但是卻疏於對更改後的程式碼進行測試,導致出現了細微但較為嚴重的BUG,應當進行深刻反省。
五、感想&小結
Unit1說實話個人做起來還是比較艱難的,主要原因有以下幾點:
- 對文法分析的不熟悉,花費了6~7個小時理解並實現了遞迴下降法
- 對面向物件的思想不熟悉,導致一部分程式碼在一開始不符合面向物件的思想,花費了較多時間閱讀《重構:改善既有程式碼設計》並進行重構
- 對測試方法不熟悉,導致第三次作業出現了嚴重BUG
雖然艱難,不過在完成本單元的學習後還是收穫滿滿:)