BUAA_OO_Unit1 表示式化簡總結
BUAA_OO_Unit1 表示式化簡總結
一、綜述
面向物件課程的第一單元的主題是對設定規則的表示式進行規則化的化簡,是我們從面向過程程式設計到面向物件程式設計思想轉變的第一站,主要考察了對Java語言和課程系統的運用以及面向物件思想的初步實踐。
本單元共三次作業,每次作業都是在上一次作業基礎上做一些更新與迭代,即增加一些新的規則,使表示式的結構更加複雜,有效訓練了同學們對問題結構的分析和設計能力。
二、作業分析
1. Homework 1
1.1 作業要求
讀入一個只包含單層括號的單變量表達式,輸出恆等變形展開括號後的表示式。表示式中只包含運算子加(+)、減(-)、乘(*)和乘方(**),輸出的表示式可以在符合輸出形式的基礎上進行恆等化簡。
1.2 架構分析
整體採用對錶達式解析,利用棧轉化成字尾表示式,利用自己構造的儲存結構進行儲存,最後進行從下到上的運算過程,最後將得到的表示式輸出。
解析>>儲存
分析作業,得到表示式的架構,一個表示式由若干個項(可以是一個)通過加號或減號連線,一個項由若干個因子(可以是一個)通過乘號連線。因子包括變數因子、常數因子和表示式因子。實際上,通過分析,所有的表示式都可以表示為
同樣地,每一個項可以表示為
實際上,變數因子和常數因子都可以用上述項的格式表示。
所以我構建了一個 Expr 類,其中包含屬性Hashmap<Integer, Term> terms,用來儲存每一個項。同時構建了一個Term 類,其中包含屬性ceofficient和power,分別表示係數和指數。最後所有的結構運算都可以表示成兩個 Expr 之間的運算。
儲存>>運算
運算的過程就是拆除括號的過程,同時我將合併同類項的工作放在運算過程中。通過建立一個運算類Operator,Operator類包含屬性left和right,表示參與運算的兩個表示式(冪運算的right是指數)。下面介紹Operator類的各種方法。
-
public Hashmap<Integer, Term> OpAdd()
先將left Expr的Hashmap深拷貝入一個新的Hashmap<Integer, Term> addterms中,然後通過遍歷right Expr的Hashmap,取出一個Term,通過遍歷addterms去尋找同指數的Term(即尋找同類型),如果找到同類項,則改變addterms中相應Term的係數(係數相加);若未找到同類項則直接向addterms中新增該Term。
-
public Hashmap<Integer, Term> OpSub()
和加法的方法類似。
-
public Hashmap<Integer, Term> OpMul()
遍歷right Expr的Hashmap,取出一個Term,然後遍歷left Expr的Hashmap,取出一個Term,將兩個Term的指數相加,係數相乘,得到新的coefficient和power,構建新的Term,存入新的Hashmap<Integer, Term> multerms,然後不斷復現加法合併同類項的方法。
-
public Hashmap<Integer, Term> OpPower()
在這裡和上述的運算方法不同,這不是兩個Expr的運算,而是一個Expr和一個指數的運算。先建立新的Hashmap<Integer, Term> powerTerms。
這裡需要對指數進行特殊判斷,如果指數為0,則建立新的Term
1*x^0
,存入Hashmap並輸出。如果指數為1,則輸出Expr的Hashmap;如果指數大於1,則通過迴圈進行多次OpMul的過程。
運算>>輸出
通過運算已經得到了最後的表示式Last Expr,由於缺乏經驗,筆者將輸出寫在MainClass的一個函式裡,最後導致MainClass的程式碼量過大。
筆者在輸出時做了一些細節上的化簡,包括
- 如果表示式存在係數為正的Term,則表示式第一項輸出係數為正的Term時可以省略輸出其係數的正號。先將Last Expr的Term根據指數的正負分別存入Arraylist
posTerms 和ArrayListnegTerms ,如果兩個容器都為空,則輸出0;若不為空則先後輸出posTerms和negTerms的內容。 - 一些更加細節的內容,x*x比x**2更短,指數為1時可以省略等等,不在此贅述。
1.3 複雜度分析
方法複雜度:
methed | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Term.Term(String) | 2.0 | 1.0 | 1.0 | 2.0 |
Term.Term(BigInteger, Integer) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.subCoefficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getCoefficient() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.addCoefficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parserTerm(String) | 2.0 | 2.0 | 2.0 | 2.0 |
Parser.parserStr(String) | 1.0 | 1.0 | 1.0 | 8.0 |
Parser.parserOp(String) | 7.0 | 7.0 | 6.0 | 7.0 |
Parser.Parser(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Operator.opSub() | 5.0 | 1.0 | 4.0 | 4.0 |
Operator.opPower(Integer) | 7.0 | 3.0 | 5.0 | 5.0 |
Operator.opMul() | 7.0 | 1.0 | 4.0 | 4.0 |
Operator.Operator(Expr, Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Operator.opAdd() | 5.0 | 1.0 | 4.0 | 4.0 |
MainClass.out(ArrayList, ArrayList) | 46.0 | 1.0 | 20.0 | 20.0 |
MainClass.main(String[]) | 5.0 | 1.0 | 5.0 | 5.0 |
Expr.setTerms(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.Expr(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 87.0 | 29.0 | 62.0 | 71.0 |
Average | 4.35 | 1.45 | 3.1 | 3.55 |
類複雜度:
class | OCavg | OCmax | WMC |
---|---|---|---|
Expr | 1.0 | 1.0 | 3.0 |
MainClass | 12.0 | 19.0 | 24.0 |
Operator | 3.6 | 5.0 | 18.0 |
Parser | 4.75 | 9.0 | 19.0 |
Term | 1.1666666666666667 | 2.0 | 7.0 |
Total | 71.0 | ||
Average | 3.55 | 7.2 | 14.2 |
通過分析,因為筆者將輸出這一結構放在了MainClass中,導致MainClass中的方法和類的複雜度嚴重超標,使得這一部分程式碼過於雜糅,實際上是因為還未從面向過程編碼轉換到面向物件程式設計。
2. Homework 2
2.1 作業要求
第二次作業在第一次作業的基礎上添加了三角函式、自定義函式和求和函式。
2.2 架構分析
整體同樣採用對錶達式解析,利用棧轉化成字尾表示式,利用自己構造的儲存結構進行儲存,最後進行從下到上的運算過程,最後將得到的表示式輸出。
解析>>儲存
實際上,通過分析,對於最終表示式的結構依然可以用一個公式表示:
只不過題目對三角函式內部的因子做了一個限制,只能是常數因子和冪函式因子。
同樣地,每一項可以表示為
筆者建立了一個Expr類,含有ArrayList
儲存>>運算
第二次作業的運算和第一次作業大致相似,不過需要在運算合併同類型時,需要在Term類中重寫比較方法,在三角函式類中寫一個比較方法。
運算>>輸出
因為偷懶,筆者依然將輸出寫在了MainClass裡。第二次作業的輸出比第一次作業多了三角函式的部分,所以細節的部分要比第一次作業多很多,特別是筆者比較簡單的儲存結構帶來的 小細節問題就會很多,比如Term中Hashmap為空的情況,或者Hashmap每一項的值全為0(即三角函式的指數為0)的情況等等。
2.3 複雜度分析
方法複雜度:
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.Cos(BigInteger, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.getCoefficient() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.Expr(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.setTerms(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Operator.Operator(Expr, Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.Parser(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.add(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.cons(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.mul(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.neg(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.pos(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.pow(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.sub(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin(BigInteger, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.getCoefficient() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.Term(BigInteger, BigInteger, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.addCeofficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getCoefficient() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.subCeofficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parserStr(String) | 1.0 | 1.0 | 1.0 | 10.0 |
Parser.parserTerm(String) | 2.0 | 2.0 | 2.0 | 2.0 |
Term.Term(String) | 2.0 | 1.0 | 1.0 | 2.0 |
MainClass.judge(HashMap) | 3.0 | 1.0 | 2.0 | 3.0 |
Term.find(Factor, BigInteger) | 4.0 | 3.0 | 3.0 | 4.0 |
Cos.Cos(String) | 5.0 | 1.0 | 3.0 | 4.0 |
Sin.Sin(String) | 5.0 | 1.0 | 3.0 | 4.0 |
Cos.compareTo(Factor) | 6.0 | 7.0 | 4.0 | 7.0 |
Parser.cos(String) | 6.0 | 1.0 | 4.0 | 4.0 |
Sin.compareTo(Factor) | 6.0 | 7.0 | 4.0 | 7.0 |
Operator.opPower(Integer) | 7.0 | 3.0 | 5.0 | 5.0 |
Parser.sin(String) | 7.0 | 1.0 | 6.0 | 6.0 |
Parser.parserOp(String) | 9.0 | 9.0 | 8.0 | 9.0 |
MainClass.main(String[]) | 10.0 | 1.0 | 8.0 | 8.0 |
Operator.opMul() | 13.0 | 5.0 | 6.0 | 6.0 |
Operator.opAdd() | 15.0 | 4.0 | 9.0 | 9.0 |
Operator.opSub() | 15.0 | 4.0 | 9.0 | 9.0 |
Term.compareTo(Term) | 18.0 | 6.0 | 5.0 | 6.0 |
MainClass.outneg(ArrayList, StringBuilder) | 21.0 | 1.0 | 10.0 | 10.0 |
MainClass.outpos(ArrayList, StringBuilder) | 22.0 | 1.0 | 11.0 | 11.0 |
MainClass.print(HashMap, StringBuilder) | 29.0 | 1.0 | 11.0 | 11.0 |
Operator.mulTri(ArrayList>) | 39.0 | 1.0 | 17.0 | 17.0 |
Total | 245.0 | 90.0 | 160.0 | 182.0 |
類複雜度:
class | OCavg | OCmax | WMC |
---|---|---|---|
Cos | 2.2857142857142856 | 7.0 | 16. |
Expr | 1.0 | 1.0 | 3.0 |
MainClass | 8.4 | 11.0 | 42.0 |
Operator | 7.666666666666667 | 16.0 | 46.0 |
Parser | 3.076923076923077 | 11.0 | 40.0 |
Sin | 2.2857142857142856 | 7.0 | 16.0 |
Term | 1.8888888888888888 | 6.0 | 17.0 |
Total | 180.0 | ||
Average | 3.6 | 8.428571428571429 | 25.714285714285715 |
由於第一次作業殘留的問題沒有改變導致MainClass的複雜度依舊很高,同時,由於結構的單一化和計算和化簡的耦合,同時第二次作業的結構更加複雜,使得計算類中的四種方法的複雜度也很大。
3. Homework 3
3.1 作業要求
第三次作業和第二次作業相比新增巢狀括號,取消三角函式和自定義函式內參數的型別限制,取消部分巢狀限制。
3.2 架構分析
第三次作業我認為和第二次作業的迭代效果最為明顯,總體結構在第二次作業上需要做的修改相對要少,主要是增加了多次遞迴呼叫的思想和方法。
解析>>儲存
表示式:
項:
在計算的過程中,合併同類項需要呼叫三角函式的比較方法,三角函式的比較需要呼叫Expr的比較方法,Expr的比較又需要呼叫Term的比較方法,以此遞迴進行比較。
儲存>>輸出
因為儲存出現了遞迴的情況,需要對輸出端做一些優化,即實現輸出方法的遞迴呼叫。最終要實現的是表示式的輸出,由於Expr中含有三角函式,三角函式中又含有Expr,所以需要遞迴呼叫輸出。同時在三角函式類的輸出方法上做一些判斷,如果三角函式中的Expr符合表示式因子的定義,則需要加上括號輸出。
3.3 複雜度分析
方法複雜度:
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Operator.mulTri(ArrayList>) | 39.0 | 1.0 | 17.0 | 17.0 |
Expr.type() | 28.0 | 9.0 | 9.0 | 11.0 |
Lexer.outTri(HashMap, StringBuilder) | 35.0 | 1.0 | 11.0 | 11.0 |
Term.compareTo(Term) | 28.0 | 10.0 | 6.0 | 10.0 |
Lexer.outPos(ArrayList, StringBuilder) | 20.0 | 1.0 | 9.0 | 9.0 |
Operator.opAdd() | 15.0 | 4.0 | 9.0 | 9.0 |
Operator.opSub() | 15.0 | 4.0 | 9.0 | 9.0 |
Parser.parserStr(String) | 9.0 | 1.0 | 9.0 | 9.0 |
Term.isSimilar(Term) | 27.0 | 9.0 | 6.0 | 9.0 |
Lexer.outNeg(ArrayList, StringBuilder) | 19.0 | 1.0 | 8.0 | 8.0 |
Expr.compareTo(Expr) | 9.0 | 3.0 | 5.0 | 6.0 |
Operator.opMul() | 13.0 | 5.0 | 6.0 | 6.0 |
Operator.opPower(Integer) | 7.0 | 3.0 | 5.0 | 5.0 |
Lexer.Lexer(Expr) | 4.0 | 1.0 | 4.0 | 4.0 |
Lexer.out() | 5.0 | 2.0 | 4.0 | 4.0 |
Parser.cos(String) | 6.0 | 1.0 | 4.0 | 4.0 |
Parser.sin(String) | 6.0 | 1.0 | 4.0 | 4.0 |
Cos.compareTo(Factor) | 2.0 | 3.0 | 1.0 | 3.0 |
Expr.count(HashMap) | 3.0 | 1.0 | 2.0 | 3.0 |
Expr.getNew() | 3.0 | 1.0 | 3.0 | 3.0 |
Lexer.judge(HashMap) | 3.0 | 1.0 | 2.0 | 3.0 |
Sin.compareTo(Factor) | 2.0 | 3.0 | 1.0 | 3.0 |
Term.find(ArrayList) | 3.0 | 3.0 | 2.0 | 3.0 |
MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parserTerm(String) | 2.0 | 2.0 | 2.0 | 2.0 |
Term.Term(String) | 2.0 | 1.0 | 1.0 | 2.0 |
Cos.Cos(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.getExpr() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.Expr(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.setTerms(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Operator.Operator(Expr, Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.Parser(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.add(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.mul(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.neg(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.nul(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.pos(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.pow(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.sub(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.getExpr() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.Term(BigInteger, BigInteger, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.addCeofficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getCoefficient() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.subCeofficient(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 306.0 | 97.0 | 165.0 | 183.0 |
類複雜度分析:
class | OCavg | OCmax | WMC |
---|---|---|---|
Cos | 1.5 | 3.0 | 6.0 |
Expr | 3.5714285714285716 | 11.0 | 25.0 |
Lexer | 6.333333333333333 | 11.0 | 38.0 |
MainClass | 2.0 | 2.0 | 2.0 |
Operator | 7.666666666666667 | 16.0 | 46.0 |
Parser | 2.25 | 9.0 | 27.0 |
Sin | 1.5 | 3.0 | 6.0 |
Term | 3.0 | 10.0 | 30.0 |
Total | 180.0 | ||
Average | 3.6 | 8.125 | 22.5 |
通過觀察,不難發現儲存結構的單一會造成一些類和方法的複雜度呈現驚人的數值。這說明我的結構並沒有體現更加多元和具體的層次化。
4. UML類圖和評價
三次作業的架構大體相同,在這裡附上第三次作業的UML類圖:
三、Bug分析
1.Bug
1.1 分析bug策略
1.先利用指導書上的基本樣例測試程式的基本功能。
2.出現bug時,將程式碼分成三個部分,即解析、儲存(包含計算過程)和輸出,分別設定斷點,觀察三部分的容器內的物件是否是自己想要的,再進行細緻尋找bug。
1.2 三次作業的bug
Homework 1: 因為還未弄清深克隆和淺克隆的區別,導致計算時各種資料的管理沒做好。通過討論區解決了問題;
Homework 2: 由於第一次提交的版本延用了第一次作業的指數用Integer型別,導致在跑同學給的測試點時出問題了,對整個程式的相關型別都進行了BigInteger的替換。
Homework 3: 第一次由於疏忽沒有注意合併同類項時並不是相等的項進行合併(係數可以不一樣)。
2.Debug
拿到程式碼後,先測試幾組自己測試程式碼的樣例,確保程式碼的基本功能(基本沒問題),然後還是從解析、計算和優化三個方面測試,特別是優化的地方(不僅優化的地方容易出現邏輯上的bug,也可以學習同學優化的方法)。最後就是資料邊界點的測試。
四、架構設計體驗
從第一次作業到第三次作業的迭代,我充分體驗到了“從無到有”的過程。我覺得三次作業裡面,第一次作業的架構與設計的過程是最讓我煎熬的一次,由於剛剛接觸這類問題,腦子裡面雖然有很多想法,比如以什麼樣的結構進行儲存、如何進行簡化等等,但是在實現起來都充滿困難。第一次作業我的架構設計持續了四天,直到週五才開始寫程式碼。
第二次作業和第三次作業相對於第一次作業有了新的元素新增,但是我似乎只是添加了一些新的類和新的方法,在大體上的架構沒有做出很大的變動。
從這三次作業看來,我的架構可以解決課程上的問題,但是從複雜度和層次化分析,我的架構看似邏輯清晰,實際上並沒有很好地給更多的表示式結構分具體的層次,導致很多地方的耦合度非常高、程式碼量非常大,如果出現了更多的形式上的變動,我的架構可能就無法實現。同時,我並未完全地理解面向物件的思想,這看似面向物件的程式本質上保留著面向過程的痕跡。比如其實可以對各種計算分別建立一個類,我卻把四種計算全部雜糅在一個類裡。
五、心得體會
1.在編寫程式碼前要充分設計架構,在設計架構時比較要考慮本次作業的功能,還要適當思考下次作業可能新加的功能。設計架構能為編寫程式碼時帶來很多便利。
2.通過本單元的學習,明白了可以通過細化結構來降低各部分的複雜度,可以更少地出現錯誤。
3.測試的重要性,希望能夠在未來學習自動化測試。
4.面向物件的思維模式,通過程式碼資料合理封裝、系統性處理,從而實現資料的安全性、重用性以及程式的可擴充套件性。這是我在本單元對面向物件思維的簡單思考。