OO Unit1 Expression Simplification
設計
本單元三次作業我都採用了為優化服務、為計算服務的架構,採用這樣架構的原因是:最後的輸出實際上只是對原表示式的資料運算結果進行輸出,不涉及原輸入的文法的儲存,因此完全略去了對儲存的需求,採用邊解析邊計算的策略。
這樣的架構優勢在於:
-
只涉及對一個儲存單元的運算,只需要實現對儲存單元的運算即可,運算結構相對統一
-
為優化服務,優化只涉及儲存單元內部的運算
-
頂層結構簡單,解析部分外只有計算類
這樣簡化的結構也帶來一些問題:
-
可擴充套件性較差,每次作業都只是當前需求的區域性較優解,很難適應新的需求
-
儲存結構上耦合度過高,導致需求變化後儲存結構都面臨重構
-
繼承關係較少,抽象層次不夠清晰
hw1
第一次作業難度較低,主要體現了整體設計上的方法:遞迴下降法。這一文法分析策略適用於上下文無關文法,也就是LL文法。我們的文法實際上是一種一元相關的Markov chain,每一個詞法單元唯一確定了後面的詞法結構,因此只需要完成對各個語法結構的解析即可。句法結構的下降體現於此,遞迴則體現在括號,括號內的層次又作為了高層次結構,需要再次遞迴,對括號內句法進行分析。規定了括號層數,實際上規定了遞迴表示式的遞迴樹深度不能超過1。
hw2
第二次作業難度驟增,為了更好體現工廠模式,我對解析過程進行了重構
-
工廠模式:解析層次上,設定了一個工廠類產生Factor,可以作為函式引數的類實現getParameter
-
優化導向:儲存層次上,設定三個層次結構,優化有關的層次結構集中於MetaData一個部分,也就是相同x指數對應的三角多項式(Trigonometric polynomial)內部進行合併
這樣的設計實際上有一定的冗餘度,而且並沒有在儲存層次上進行分割與抽離,只是從結構上看起來更像OOP了一點,並沒有實質上將不同物件的特徵進行提取
UML類圖如下,這個圖中可以很清晰的看到遞迴下降的過程。
儲存層次結構的複雜度如下:
可以看到,把優化的任務集中在一個類上,導致了優化相關的部分(sinMergeable、Mergeable、simplify)等複雜度都過高了,這一點為後面出現bug與debug都埋下了一定的隱患。
hw3
第三次作業雖然需求上完全可以在第二次的基礎上增量開發,但我感受到了極簡主義的召喚,以及預估了原結構優化的難度之大,故進行了整體重構。
我將所有解析的過程重新歸於Parser處理,同樣採用解析的同時計算的策略,同時為了一般化處理,我將三角函式內部視為一個多項式進行儲存,比較時比較二者差異即可
這次的重構在我看來充滿爭議性,優勢在於:
-
簡化了不必要的類,解析任務集中統一化,避免了套用工廠模式之嫌(化作了抽象工廠,更簡單啦)
-
儲存層次結構使用HashMap實現,找key的過程更簡化,省去了無用而繁瑣的維持有序的過程
問題在於:
-
因為是最後一次單元,故沒有考慮可擴充套件性
-
parser表示壓力山大
UML類圖如下:
複雜度分析:
本次作業我做了勾股定理(Pythagorean theorem),優化asin(x)**2+bcos(x)**2
與二倍角(Double angle),優化2**n*sin(x)**n*cos(x)**n
上圖中複雜度較高的部分都是我用於化簡的部分,因此帶來了較高的複雜度我認為是難以避免的。同時我設計了全域性優化開關,可以通過開關控制是否優化,因此若是優化部分出鍋可以通過關閉全域性優化開關簡單解決
測試
本單元三次作業我採用了兩種不同的測試方案。
隨機生成普通資料
第一次作業特殊情況與優化較少,邊緣資料使用隨機生成的資料可以達到較好的覆蓋,因此我使用java內建隨機數生成隨機生成資料進行測試。隨機數生成的過程實際就是解析的逆過程,可以稱之為“遞迴上升法”句法構造,生成的資料也可以通過引數的設定完成customerize。
//隨機資料生成
import java.util.Random;
public class Generater {
public static final int MAX_RECURSION_DEPTH = 1;
public static final int MAX_FACTOR_COUNT = 100;
public static final int MAX_POLY_INDEX = 20;
public static final int MAX_POWER_INDEX = 20;
private static final int MAX_NUM_LEN = 5;
private Random random;
public Generater() { this.random = new Random();}
//最長長度與長度等級
public String generatePoly(int maxLen, int lenLvl, int curdepth) {
StringBuilder sb = new StringBuilder();
int r = random.nextInt();
if (r % 3 == 0) { sb.append('+'); }
else if (r % 3 == 1) { sb.append('-'); }
sb.append(generateTerm(maxLen * 2 / 3, lenLvl - 1, curdepth));
String s = generateTerm(maxLen * 2 / 3, lenLvl - 1, curdepth);
// 1/EPLL probability of termination each time
while (sb.length() + s.length() + 1 < maxLen && r % lenLvl != 0) {
if (r % 2 == 0) { sb.append('+'); }
else { sb.append('-'); }
sb.append(s);
s = generateTerm(maxLen * 2 / 3, lenLvl - 1, curdepth);
}
return sb.toString();
}
public String generateTerm(int maxLen, int lenLvl, int curdepth) {
StringBuilder sb = new StringBuilder();
int r = random.nextInt();
if (r % 3 == 0) { sb.append('+'); }
else if (r % 3 == 1) { sb.append('-'); }
sb.