1. 程式人生 > 其它 >BUAA OO 第一單元總結

BUAA OO 第一單元總結

一、第一次作業

(1)類結構設計

第一次題目架構比較簡單,具體結構設計如下(圖1):

  • 基本資料型別:
    • Polynomial:用Hashmap<int(指數), BigInteger(係數)>將表示式/項/因子資料內容統一形式
      • 運算方法:加法add、乘法mul、乘方pow
      • 格式化輸出:toString方法將其內容輸出(含正項提前處理),addPowerFunc負責將每個項轉化為字串形式。
  • 表示式解析:
    • Lexer :使用正則表示式(?<num>(?<=[^\\dx)]|^)[+-]?\\d+|x)|(?<opt>\\*\\*|[)(+\\-*])
      匹配每個有效字串,得到形如 {"-","-","-2","*","(","x",")"} 的字串序列
    • parser:使用遞迴下降法對得到的字串序列進行解析,得到表示式/項/因子例項物件。
  • 表示式儲存
    • Expr:表示式
      • 運算方法:updateterms中的Polynomial相乘後,在乘power次冪並記錄到res中;negateres取負。
      • 更新方法:addTerm增加新的項
      • 輸出方法:toString呼叫res.toString
    • Term:項
      • 運算方法:updatefactors中的Polynomial並記錄到res中,negateres取負。
      • 更新方法:addFactor
        增加新的因子
    • Factor(介面):包含ExprValueFactor
      • 運算方法:negateres取負
      • 取值方法:getRes獲得Factor中資料值
      • 介面實現類:
        • Expr:表示式
        • ValueFactor:常數(冪次為0)以及冪函式

(2)度量分析

從圖中可以看出PolynomialParser類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:表示式
      • 運算方法:updatePowerrespower次冪並記錄到res中;updateterms中的Polynomial相乘後記錄到res中;negateres取負。
      • 更新方法:addTerm增加新的項
      • 輸出方法:toString呼叫res.toString
      • 化簡方法:simplyfy將呼叫res.simplyfy
      • 判斷相同方法:hashCode equal
      • 複製方法:deeplClone創造一個新的Expr
    • Term:項
      • 運算方法:updatefactors中的Polynomial並記錄到res中,negateres取負。
      • 更新方法: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
      • 判斷資料型別方法:equalX2equalConstantequalExpr
    • 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:表示式
      • 運算方法:updatePowerrespower次冪並記錄到res中;updateterms中的Polynomial相乘後記錄到res中;negateres取負。
      • 更新方法:addTerm增加新的項
      • 輸出方法:toString呼叫res.toString
      • 化簡方法:simplyfy將呼叫res.simplyfy
      • 判斷相同方法:hashCode equal
      • 複製方法:deeplClone創造一個新的Expr
    • Term:項
      • 運算方法:updatefactors中的Polynomial並記錄到res中,negateres取負。
      • 更新方法: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較高,PolyParser原因同第一次作業,PolySimplyfy原因為每次化簡時需要遍歷所有鍵值對,同時一直化簡到無法化簡為止。

(3)Hack情況

本次強測結果比較慘痛,根本原因在於課下測試不充分。

  • 己方bug分析:本次作業個人在強測和互測共出現3個bug
    • PolyequalX2方法出錯:未考慮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

雖然艱難,不過在完成本單元的學習後還是收穫滿滿:)