1. 程式人生 > 其它 >2022面向物件第一單元總結

2022面向物件第一單元總結

第一次作業

核心類圖

架構分析

  • 主體分為解析、化簡、合併三個過程,三者之間基本解耦
  • 優化在合併過程中完成
  • 支援括號巢狀

解析

  • 核心類: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 類行數較多,因為優化主要在這兩個類裡完成,所需程式碼量較大

方法複雜度分析

  • 平均複雜度不高,耦合度較低
  • 複雜度較大的幾個方法均是用於優化的,條件判斷與迴圈結構較多

類複雜度分析

  • PolyTriangle 包含大量優化方法,迴圈結構較多,複雜度較高

資料生成

隨機資料

  • 進行了簡單迭代,增加了新出現的一些因子,新增了規則限制
  • 規則限制
    • 可使用的變數
      • 自定義函式定義: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到的情況

反思總結

迭代與重構

  • 第一次作業到第二次作業迭代的工作量較大,主要原因有三
    • 在因子層級進行了重構
    • 新增了較多因子,程式碼量翻倍
    • 做三角函式優化花了不少時間
  • 第二次作業到第三次作業幾乎沒有進行什麼修改
    • 第二次作業已經能滿足第三次的要求
    • 增加了一點三角函式優化
    • 修改了優化部分的程式碼,複雜度更低

架構的優缺點

優點

  • 程式碼邏輯清晰,不同功能類之間的耦合度較低
  • 對於表示式樹的維護較好,具有較強的魯棒性、可擴充套件性

缺點

  • 優化部分演算法簡單,效率較低,很多情況無法得到最優解
  • 在表示式樹的設計中沒有引入運算子層級,如果新增除法或其他運算子,可能會需要不小的重構

心得體會

  • 提升了面向物件思維能力和層次化設計能力
  • 學會了遞迴下降法解析表示式
  • 對於設計模式有了一定了解
  • 資料構造和自動化測試水平提高