2022面向物件第一單元總結
阿新 • • 發佈:2022-03-25
第一次作業
核心類圖
架構分析
- 主體分為解析、化簡、合併三個過程,三者之間基本解耦
- 優化在合併過程中完成
- 支援括號巢狀
解析
- 核心類:
Parser
,Lexer
- 採用遞迴下降法解析表示式,後續兩次作業在此基礎上進行迭代
-
Lexer
處理輸入字串,一次可取出一個整數或一個其他字元 -
Parser
依據表示式樹的結構進行解析,生成表示式樹之後不再對字串進行操作
化簡
- 核心類:
Expr
,Term
,Power
,Factor
,Var
- 核心類均為不可變類,化簡時會另外生成一個新的物件,不改變原有物件結構
- 自下往上化簡,減少表示式樹深度,最終得到巢狀層度為 0 的表示式(未合併同類項),例如
1+1*x+1*x+1*x**2
- 表示式樹的結構與官方的形式化表述在因子層級不同,筆者將冪函式
Power
作為項Term
的組成部分,變數Var
和表示式Expr
實現介面Factor
並作為冪函式的底數 - 與官方形式化表述的對應關係:
-
( Var ) ** 0
<==> 常數因子 -
( Var ) ** <非0指數>
<==> 冪函式因子 -
( Expr ) ** <指數>
<==> 表示式因子
-
合併
- 核心類:
Poly
,Power
- 利用
HashMap<BigInteger, BigInteger>
進行合併同類項,key
為冪函式的指數,value
優化
-
係數優化
1*x => x
-1*x => -x
-
指數優化
x**0 => 1
x**1 => x
x**2 => x*x
-
正項提前
-1+x => x-1
基於度量的分析
程式碼規模分析
- 總行數小於 500 行,程式碼規模不大
- 類行數均小於 100 行,功能相對分散
方法複雜度分析
- 平均複雜度不高,方法間耦合度低
- 複雜的較大的方法主要是項的化簡以及表示式的字串化函式
類複雜度分析
- 平均迴圈複雜度不高,
Poly
在合併同類項以及輸出優化時進行過多次迴圈,因此複雜度相對較高
測試資料
隨機資料
- 生成方法:表示式逆解析
-
範圍限制
- 最大項數
- 最大因子數
- 括號巢狀數:官方測試資料不允許括號巢狀,但在本地測試時可以允許
- 最大指數:不超過 8
exp( expr ) = max{ exp( term ) }
exp( term ) = sum{ exp( power ) }
常量池資料
- 在各個層級將隨機生成改為從常量池中選取
- 介於隨機資料和特殊資料之間,屬於半隨機資料
特殊資料
- 整數溢位:
(9999999999999999999999999)**8
- 指數上限:
(x+x+x+x+x+x+x+x)**8
- 全 0 資料:
0+0*(0*0*0+0*0*0)*(0)**0+0*0*(0)**0
第二次作業
核心類圖
架構分析
- 主體分為解析、化簡、合併三個過程,三者之間基本解耦
- 優化在化簡和合並過程中完成
- 支援因子巢狀和函式巢狀呼叫,可直接作為第三次作業
解析
- 核心類:
Parser
,Lexer
-
Lexer
新增nextIdentifier
方法,一次讀出一個識別符號,用於獲取變數、函式名稱 -
Parser
根據此次作業表示式樹的結構進行了部分重構與迭代
化簡
- 核心類:
Expr
,Term
,Factor
,Constant
,Power
,ExprFactor
,Triangle
,Sum
,FuncCall
,FuncDef
,Var
- 相較於上次作業,在因子層級進行了重構,與官方形式化表述完全一致
-
Factor
介面-
simplify
方法:返回型別不一定與原型別相同,例如Power
類物件x**0
在化簡後可以返回Constant
類物件1
-
substitute
方法- 用於進行函式引數帶入,將
Var
物件替換成Factor
物件 - 只有在
Power
層級才進行帶入,其他層級則繼續向下傳遞或停止
- 用於進行函式引數帶入,將
-
合併
- 核心類:
Poly
,Factor
- 重構
Poly
類,利用HashMap<Factor, BigInteger>
儲存項,HashMap<HashMap<Factor, BigInteger>, BigInteger>
儲存表示式 x*x*sin(2) + 3*cos(1) => HashMap{ <HashMap{ <x, 2>, <sin(2), 1> }, 1>, <HashMap{ <cos(1), 1> }, 3> }
優化
-
相較於上次作業,新增三角函式相關的各種優化(因為資料限制,部分優化到第三次作業才起作用)
-
符號優化
sin((-x)) => -sin(x)
cos(-1) => cos(1)
-
括號巢狀優化
sin((2)) => sin(2)
sin((1*x*x)) => sin(x**2)
-
公式優化
cos(x)**2 + sin(x)**2 => 1
1 - sin(x)**2 => cos(x)**2
2*sin(1)*cos(1) => sin(2)
基於度量的分析
程式碼規模分析
-
Poly
類和Triangle
類行數較多,因為優化主要在這兩個類裡完成,所需程式碼量較大
方法複雜度分析
- 平均複雜度不高,耦合度較低
- 複雜度較大的幾個方法均是用於優化的,條件判斷與迴圈結構較多
類複雜度分析
-
Poly
與Triangle
包含大量優化方法,迴圈結構較多,複雜度較高
資料生成
隨機資料
- 進行了簡單迭代,增加了新出現的一些因子,新增了規則限制
- 規則限制
- 可使用的變數
- 自定義函式定義:x, y, z
- 主表示式:x
- 求和函式:已知的可使用變數 + i
- 禁止函式呼叫
- 自定義函式定義和求和函式
- 自定義函式呼叫(第三次作業允許)
- 可使用的變數
常量池資料
- 新增三角函式池
特殊資料
- 針對本次作業規則以及自己的優化過程,新增了一些手動構造的資料
- 括號巢狀:
sin(((f(g(f(h(-1))))))), 其中f(x) = g(x) = h(x) = (x)
- 三角化簡:
- 符號化簡:
sin(-1)**2, cos(-1)**2, sin((-x-1))
- sin2 + cos2 = 1:
cos(x)**2+sin(x)**4+cos(x)**2*sin(x)**2
- 1 - sin2 = cos2:
sin(x)**2*cos(x)**2-sin(x)**2
- sin 二倍角公式:
-3*2*sin(1)**2*cos(1)**2
- 符號化簡:
第三次作業
核心類圖
架構分析
- 主體分為解析、化簡、合併三個過程,三者之間基本解耦
- 優化在化簡和合並過程中完成
解析
- 較上次作業沒有變化
化簡
- 較上次作業沒有變化
合併
- 核心類:
Poly
,Item
,Factor
-
Item
類- 繼承自
HashMap<Factor, BigInteger>
類 - 主要目的是為了縮短類名,並增加兩個方法用於三角函式優化,使用更加方便
- 繼承自
優化
- 新增公式優化:
1 - 2*sin(1)**2 => cos(2)
- 公式優化策略
- 貪心演算法
- 利用優先佇列
PriorityQueue<Poly>
,按字串長度排序 - 每一輪嘗試所有可能的公式變形,生成新的
Poly
物件扔進優先佇列。結束後取出隊首元素,若該表示式長度小於原本表示式,則進行替換,並繼續化簡;否則結束化簡。
基於度量的分析
程式碼規模分析
- 由於第二次作業已滿足本次作業要求,所以並未有太大改動,程式碼行數基本不變
方法複雜度分析
- 對優化方法做了一些改動和優化,所以複雜度略有下降
- 複雜度較大的方法仍集中在優化部分
類複雜度分析
- 引入的
Item
類分擔了原本Poly
類的部分功能,使Poly
複雜度有所下降 - 各項平均複雜度也均有降低
資料生成
隨機資料
- 較上次作業沒有太大改動
常量池資料
- 較上次作業沒有太大改動
特殊資料
- 針對新增的優化方法以及並未實現的優化方法構造了一些資料
- cos 二倍角公式:
2*cos(x)**2*cos((2*x))+sin((2*x))**2+cos((2*x))
- 和差角公式:
2*cos(x)**2*cos((2*x))+sin((2*x))**2+cos((2*x))
測試策略與bug分析
測試策略
基本流程
- 主要採用黑盒測試 + 隨機轟炸
- 自動測試:命令列編譯、執行,使用
subprocess
模組 - 正確性檢查:利用
sympy
庫 - 格式檢查:利用正則表示式
對拍測試
- 本地三人對拍,用於檢測自己程式的正確性
- 可同時檢測三個人的 bug,並且三個人同時跑的話效率能高 3 倍
多人測試
- 用於互測,以自己的程式為標準與其他人對拍
- 利用迴圈結構進行序列測試
資料分析
- 通過資料拆解或答案比對,構造出引起錯誤的極小項
- 隨後調整資料生成器或者修復他人bug
- 避免不斷 hack 同質 bug,增加 hack 效率
本人bug
- 三次強測和互測中均未發現 bug
- 本地測試中遇到過的一些問題
- 引數代入問題
- 資料:
0 f(y, x) = x*sin(y) f(x, 1)
- 原因:原本
substitude
方法的引數表是(Var, Factor)
,一次只能代入一個引數。因此會先代入y=x
,得到x*sin(x)
,再代入x=1
,得到sin(1)
,與答案sin(x)
不符 - 解決辦法:修改引數表為
(HashMap<Var, Factor>)
,同時傳入所有要代入的引數。並且能提升效率(只遍歷一遍表示式樹,原本可能遍歷 1 ~ 3 遍)
- 資料:
- 優化後變長
- 資料:
-3*2*sin(x)**2*cos(x)**2
- 原因:不進行二倍角優化,結果是
-6*sin(x)**2*cos(x)**2
;進行二倍角優化,結果是-3*sin((2*x))*sin(x)*cos(x)
,答案更長 - 解決辦法:引入優先佇列
PriorityQueue
來儲存優化的結果,並與原表示式進行長度比較
- 資料:
- 引數代入問題
他人bug
- 第一次作業:hack 1 人 1 個 bug
- 第二次作業:hack 4 人 8 個 bug
- 第三次作業:hack 3 人 6 個 bug
- 完全採用黑盒測試,並未根據他人程式碼構造針對性樣例
- 根據公開的hack結果,只有第三次作業的一個點並未hack到,原因是沒有進行嚴格的格式檢查;總體來說測試資料的強度還可以,覆蓋率較高,並沒有出現因資料問題而沒有hack到的情況
反思總結
迭代與重構
- 第一次作業到第二次作業迭代的工作量較大,主要原因有三
- 在因子層級進行了重構
- 新增了較多因子,程式碼量翻倍
- 做三角函式優化花了不少時間
- 第二次作業到第三次作業幾乎沒有進行什麼修改
- 第二次作業已經能滿足第三次的要求
- 增加了一點三角函式優化
- 修改了優化部分的程式碼,複雜度更低
架構的優缺點
優點
- 程式碼邏輯清晰,不同功能類之間的耦合度較低
- 對於表示式樹的維護較好,具有較強的魯棒性、可擴充套件性
缺點
- 優化部分演算法簡單,效率較低,很多情況無法得到最優解
- 在表示式樹的設計中沒有引入運算子層級,如果新增除法或其他運算子,可能會需要不小的重構
心得體會
- 提升了面向物件思維能力和層次化設計能力
- 學會了遞迴下降法解析表示式
- 對於設計模式有了一定了解
- 資料構造和自動化測試水平提高