第一單元的三次做業都圍繞着表達式求導,經過迭代開發的方式完成了支持冪函數和三角函數(僅包括sin和cos)及其嵌套的表達式求導程序。下面是我對這一單元學習的一個總結。javascript
本次做業難度較低,只要求了簡單冪函數的求導,我使用了四個類來完成,分別爲主類、解析器類、項類和多項式類。java
數據存儲
分別經過項類和多項式類完成。每一個項對象存儲一個冪函數項,包括其指數和係數;多項式類中用一個TreeSet對象存儲若干個項,構成多項式。正則表達式
輸入
主類中獲取輸入,交給單獨的解析器類進行解析;分別經過正則表達式獲取係數、指數和常數,將其組成項和多項式。算法
求導
在主類中調用多項式類的求導方法,分別對TreeSet中的每一項調用各自的求導方法,獲取求導結果後建立新的TreeSet,並更新類內的域。markdown
輸出
在主類中調用多項式類的輸出方法,分別得到TreeSet中每一項的輸出字符串,將其鏈接並輸出。數據結構
第一次做業UML圖以下:
架構
複雜度分析結果以下:
框架
因爲本次做業要求較爲簡單,只須要對冪函數的求導,本次代碼複雜度尚可,各個方法的複雜度都在可接受範圍以內,四個類的內聚度也較高。async
本次做業在公測和互測中均未發現bug,化簡也很到位,在強測中得到了滿分。ide
在本次做業的互測中,我嘗試手動構造了一些樣例,但都沒有hack到。最後整個room中都沒有bug發現,應該是確實沒有什麼bug。
本次做業中新增了因子之間的乘法運算和sin、cos兩個三角函數。能夠注意到本次做業有很明顯的表達式-項-因子三層結構,所以我使用了五個類,採用了先分割輸入再依次解析的方法,卻沒有考慮到可擴展性,直接致使了第三次做業的重構。
數據存儲
最基本的單元爲因子,存儲其係數(僅限於常數項)、指數;因爲沒有采用繼承和多態,在因子對象中還須要一個類型域,以標識是冪函數,常數仍是三角函數。
項類中存儲一個因子的TreeSet,表達式中存儲一個項的HashSet,構成三級結構。
**
替換,將連續的正負號處理爲單獨的正負號+-
將表達式分割爲項;根據*
將項分割爲因子求導和輸出
和第一次做業同樣,經過主類調用多項式對象的相應方法,多項式對象再依次調用項對象的相應方法,每一個項再依次調用因子的相應方法。
化簡
本次做業除了第一次做業中的合併同類項和正項提早輸出以外,新增了由\(sin^2x+cos^2x=1\)帶來的化簡可能。我採用的方法是經過屢次遍歷各項,找出可能的化簡狀況同時化簡。若是在完整的一次遍歷以後都沒有找到可化簡的地方,就結束化簡過程。
第二次做業UML圖以下:
Parser.getFactor(String)
和
Term.toString()
兩個方法的模塊設計複雜度超標,代表其耦合度太高,這是由於這兩個方法中存在大量特判狀況,大部分是爲了化簡輸出和讀取省略係數或指數的因子。還有幾個方法的基本複雜度超標,也有上述大量特判的緣由,另外對於
Poly.merge
方法,還存在多層循環的控制問題。這也致使了三個類的平均圈複雜度或總圈複雜度超標。
本次做業在公測中未發現bug,在互測中發現了一個bug,是因爲對-+-
的正負號化簡錯誤。本次做業化簡較爲成功,在強測中得到了94分,一些地方如\(1-cos^2x\)沒有考慮到化簡。
本次互測中只查出一個bug,是因爲其在對輸入冪函數指數10000的限制,誤將整個過程當中的指數均限制在10000如下。這也是我在開始犯得一個錯誤,歸根結底是沒有仔細閱讀指導書。根據本身在完成做業時的錯誤去尋找他人的錯誤也是一個不錯的方法。
第三次做業難度大大提高,增長了表達式的嵌套,這也使得我以前的架構基本失效,只能進行重構。本次做業我使用了12個類。
數據存儲
本次做業中有10個類是數據類,分別爲項抽象類,冪函數、常數、sin函數、cos函數基本類,混合項抽象類,二項加法、乘法類(用於處理輸入),多項加法、乘法類(用於求導、化簡和輸出)。
輸入
本次做業我採起了上學期數據結構課程中學到的棧解析中綴表達式的算法,首先將冪函數、常數、sin函數、cos函數找到並存在隊列中,將原表達式按照類型換爲單個字母(如2*x+sin((x**2+x))化爲n*p+s),再將字母看成單個操做數進行解析,最終獲得表達式樹。
二元項化多元項
經過遞歸的方式,將二元的加法、乘法項化爲多元的相應項。
求導
我在本次做業中曾嘗試在將二元項化爲多元項以前進行求導,但測試發現其時間需求太高,因而採用了先化爲多元項再求導的方法,時間複雜度獲得顯著降低。求導方法按照多態的思想,根據不一樣的類型而不一樣。
化簡
本次做業可化簡的部分實在太多,綜合考慮我只進行了合併同類項和提公因式兩方面,三角函數的相關化簡就放棄了。在個人架構下,可合併的部分是加法項和乘法項。其中乘法項只需考慮合併同類(由於不容許表達式帶指數),加法項須要考慮合併同類項和提公因式,其實在原理上是一致的。
化簡的思路仍是經過若干次遍歷,找到可合併的兩項,若是在一次完整的遍歷中沒有合併點,就結束化簡。對於乘法項,找到合併點後能夠結束遍歷直接化簡,而加法項則須要找到化簡程度最大的兩項進行化簡。
輸出
各方面考慮下,我爲每一個類設計了一個獲取係數的方法和一個獲取除係數外部分字符串的方法,在項抽象類中編寫toString()
方法將兩者合併爲輸出。
第三次做業UML圖以下:
本次做業在公測中出現了兩個問題,得分70分,在互測中也出現了其中的一個問題。一個很是低級的錯誤是我因爲思惟慣性,在對輸出進行化簡時還依照前兩次做業,將x**2
化簡爲x*x
,直接致使x*x
單獨出如今三角函數中時的格式錯誤。公測一共WA了6個點,有5個都是由於這個格式錯誤。另外一個Bug是對於棧解析表達式算法的錯誤,在回憶時出現了誤差。性能方面,個人化簡仍是很成功的,在強測經過的點中性能分均爲滿分。
本次互測中共hack到11個點,主要仍是依照本身在完成做業過程當中發生的問題進行構造樣例;此外,還構造了一些大量嵌套的樣例,然而並無hack到TLE,反倒hack到了一些格式錯誤。
其實第三次做業很適合使用工廠模式,只是當時對工廠模式優越性的理解還不夠,就沒有采用,而是將從輸入的字符串到表達式樹的過程所有放在一個解析器類中,形成了極大的複雜度。能夠將解析器類進行拆分,令其只進行找出每個最小單元的任務,剩下的將字符串轉爲項的工做能夠交給工廠完成。這樣應該能夠大大下降耦合程度。
感謝寒假pre做業
寒假的兩彈pre做業做爲熱身,效果仍是很不錯的。第一是令我開始使用面向對象的思惟方式,第二是能熟悉Java語言的基本使用。
先想清楚再寫代碼
在第二次做業中,我看完指導書就打開了第一次做業的代碼開始改,結果很顯而易見,遇到了大量的bug,添加了無數個補丁才把這個程序穩住。第三次做業若是像這樣去作那必然是原地爆炸,因此我仍是選擇了先想清楚,要使用什麼結構,怎樣解析輸入,使用什麼化簡算法,等這些大框架定下來以後,寫起代碼來也會流暢不少,只須要額外處理一些細節問題。只是這兩次做業在分數上彷佛並不支持這個結論,我選擇把它歸爲偶然。
考慮後續任務
也就是保持可擴展性。OO每一個單元的做業都是以迭代的方式進行的,若是不在以前的做業中考慮可擴展性,那每次做業都面臨着重構,工做量反而會大大提高。
第一單元的學習就這樣過去了,不得不說壓力仍是比較大的,但收穫也是不少。但願在接下來的做業中能取得更好的結果。