2022 OO 第一單元個人總結
一、前言
對於本單元的專案設計,我認為以下三點是值得深思與提升的:
-
選取合適的資料結構儲存資訊,資料結構的選取合適程度,與表示式化簡的難度以及由程式碼優化產生的bug數量息息相關;
-
採用遞迴下降的方法解析expression;
-
通過表示式預處理,更優美地書寫程式碼;
但我們不得不承認,預處理、化簡等不論是優化程式碼本身、抑或結果輸出的方式,都極大的增加了bug產生的可能性。因為優化的同時,就意味著我們考慮更少的可能性,也就存在更多的非法情況。對於 優化 與 bug 之間的矛盾,是需要我們權衡的。
對於三次作業的迭代,筆者認為對於專案的思考,主要聚焦於資料結構選取
對於遞迴下降分析法的應用,在第一次作業中也許是一件有挑戰的事,但在第二、三次作業中,也並不顯得很困難。
故而本部落格將僅對於第三次作業進行全面分享,並輔以 與第一次作業的資料結構 、 與第二次作業的程式碼優化、表示式預處理 進行對比,闡述自己的思考。
二、專案迭代的最終版本
1. 形式化表述
首先,為方便觀看與回憶,附上第三次作業的形式化表述(本部落格不再討論關於課程限定等細枝末節的內容,而是聚焦於答題思路,課程限定不過是為了方便我們完成專案)。
2. 資料結構與程式架構
程式 = 資料結構 + 演算法
資料結構
在本程式中,資料結構的選擇需要面對儲存
Factor -> Term -> Expression
對於儲存的需求,我們自然而然地建立了Num
、Power
、 Sin
、 Cos
四個類來儲存最基本的資訊。並通過Factor
這個抽象類來實現對基本類的統一。對於 Sin
與 Cos
如何儲存資訊(或者說其內部變數的資料結構)暫且不論,至少,我們擁有了這四個最基本的類,構成了可以完成本專案的最基本的資料結構。
在構造完成上一段的基本類後,事實上我們完成了最基本的Factor需求,接下來便是思考如何完成將Factor連乘為Term的資料結構構造。
近乎自然的發散思維,可以讓我們聯想到將基本類統一為一個NormalOfFactor
C*x^p*∏(Sin()^q)*∏(Cos()^r
這樣的形式(就像物理一直追求四個基本力大一統一樣)。該類通過如下 資料結構儲存資訊、方法完成Term連乘功能。
private String op;
private BigInteger coe;
private BigInteger pow;
private ArrayList<Sin> sinFactors = new ArrayList<>();
private ArrayList<Cos> cosFactors = new ArrayList<>();
public void addFactorOfNum(Num num); //事實上,可以將四個方法再次包裝成一個方法:
public void addFactorOfPower(Power power); //addFactor(Factor factor);
public void addFactorOfSinArray(Sin sin);
public void addFactorOfCosArray(Cos cos);
接下來,我們建立Term
類,用來實現Term的功能,分析Factor與完成計算。
private final ArrayList<NormalOfFactor> normalOfFactors = new ArrayList<>();
public void addFactor(Factor factor); //將Factor對與normalOfFactors中每個元素乘
根據形式化表述,我們可以將Factor分為兩類,基本類與表示式,基本類的連乘方法已在NormalOfFactor
中實現,而考慮表示式的作用,表示式的連乘功能將在Expr
類中實現。
另外,normalOfFactors
的存在,是為了滿足表示式因子的計算,如(x+1)(x+2)。
通過上面的構建,我們已經擁有了很多個Term,現在的目的是將terms,計算出expression。
expression具有兩個內容,一是將terms加在一起,二是將其計算冪次。
在不考慮化簡的情況下,功能一僅需要將每個term
的normalOfFactors
加在一起,所以在Expr
,我們仍定義normalOfFactors
而非terms
。
public void addTerm(Term term) {
for (NormalOfFactor normalOfFactor : term.getNormalOfFactors()) {
addOneOfTerm(normalOfFactor); //在新增時化簡
}
}
而對於功能二,在NormalOfFactor
類的方法下,就是相當於一個三重迴圈。
public void calForPow();
public void multiTwoNormalOfFactors(ArrayList<NormalOfFactor> normalOfFactors1,
ArrayList<NormalOfFactor> normalOfFactors2,ArrayList<NormalOfFactor> newNormalOfFactors);
private void multiNormalOfFactorToNormalOfFactors(NormalOfFactor normalOfFactor,
ArrayList<NormalOfFactor> normalOfFactors,ArrayList<NormalOfFactor> newNormalOfFactors);
private void multiTwoNormalOfFactor(NormalOfFactor normalOfFactor1,
NormalOfFactor normalOfFactor2,ArrayList<NormalOfFactor> newNormalOfFactors);
我們可以看出,此前挖的坑(Term
如何處理Expr
因子),只需要呼叫expr.multiTwoNormalOfFactors()
即可。
於是,拋去一些細枝末節的東西,資料結構的儲存便如此完成了。
現在我們討論對化簡的需求。
注意:此時未根據三角函式的性質化簡(奇偶性、三角恆等式)
化簡的思路只有一個:給定標準型,然後遞迴下降。
我們自頂而下看:
-
如何化簡兩個項相加?
-
在
addTerm
時,對於相同的normalOfFactor
完成合並(即兩個coe
相加); -
如何識別
normalOfFactor
可以合併?-
規範
normalOfFactor.toStringExcCoe
;
-
若
normalOfFactor.toStringExcCoe
相等,則認為可以合併;
-
-
最終:保證
Expr
的normalOfFactors
不存在可化簡情況。
-
-
如何化簡兩個因子相乘?
-
coe
直接相乘 -
pow
直接相加 -
sin(expr)^p
二重迴圈-
如果
expr.toString
相同,則兩個p
相加;-
expr
通過Collections.sort(normalOfFactorsToString)
提供排序規範;
-
-
-
cos(expr)^p
二重迴圈-
如果
expr
相同,則兩個p
相加;-
expr
通過Collections.sort(normalOfFactorsToString)
提供排序規範;
-
-
-
最終:保證
Term
的normalOfFactors
不存在可化簡情況。
-
-
如何確定
normalOfFactorsToString
相等?-
coe + * + normalOfFactor.toStringExcCoe
-
規範
Num.toString
; -
規範
normalOfFactor.toStringExcCoe
-
x^p
相等;-
規範
Power.toString
;
-
-
通過
Collections.sort(sinStrings)
規範∏(Sin()^q)
的String輸出順序;-
規範
Sin.toString
;
-
-
通過
Collections.sort(cosStrings)
規範∏(Cos()^q)
的String輸出順序;-
規範
Cos.toString
;
-
-
-
-
上文中省略瞭如sin(0)
, coe == 0
, pow == 1
等情況的討論。
如上述討論,我們自頂而下分析,通過Collections.sort(Strings)
這個java
自帶的函式,通過重寫toString
函式,完成了排序與優化。
演算法
預處理
對於輸入的expr
,對其進行五方面的化簡:
-
消除" "與"\t";
-
將"**"替換為"^";
-
使
expr
僅連續有一個正負號; -
使數字沒有前導零;
-
消除一次冪;
自定義函式
首先儲存函式,並進行預處理,後在使用時對引數進行替換。注意,對於次冪要加入()
防止出現表示式無括號的情況;
求和函式
對於求和函式,先將i
進行替換,後進行預處理。注意,對於次冪要加入()
防止出現表示式無括號的情況;
3. 程式碼度量
UML圖
OO度量
LOC
:Lines of Code: 類/方法程式碼規模
NAAC
:Number of Attributes: 類屬性個數
NOAC
:Number of Methods:類方法個數
CONTROL
:Number of Control statements:控制分支個數
-
程式碼統計
-
類度量
-
類複雜度度量
-
方法複雜度度量
-
類耦合度量
複雜度較低,耦合度較低,內聚情況良好
4. 對比
與第一次作業對比,增添了預處理,極大的減少了Parser
類中toParser
、toTerm
與toFactor
的複雜度,但會由此產生更多的bug。
與第二次作業相比,程式碼優化與資料結構的構造,讓程式碼變得優雅而輸出複雜度低;但同樣,存在一定的bug,並且資料結構複雜。
三、bug分析
一個最重要的bug,就是對於深淺拷貝理解不充分導致的。
對於大多數情況,如項的加減,淺拷貝即可。但對於表示式的冪次,必須要深拷貝,因為要多次運用其原表示式。
其他大多bug一個出現於化簡程式碼,另外均出現於sum類。由於在sum類代換後,未進行表示式化簡。
事實上,許多bug的產生是由於測試不充分導致的,應當進行更多、更充分的測試。
四、架構體會與感想
本單元的練習,我對遞迴下降的思想有了更深刻的理解與應用。
同時,對於OO的思想,高內聚低耦合的設計思路,有了自身的理解。