本單元的任務為求導。
即將一個含自變量x的多項式F求導成為另外一個含自變量x的多項式f。使得 dF/dx = f
為降低我們的難度,這個任務被分解成了三個階段:
(1)對冪函數進行求導(不允許嵌套)
(2)對冪函數和三角函數進行求導(不允許嵌套,三角函數中只能有x)
(3)對冪函數和三角函數進行求導(允許嵌套,三角函數中只能有因子)
一、聊聊思路
1、字符串處理:
在第一和第二個階段中,對輸入的處理時相對比較容易的,因為我們可以使用正則表達式對整個輸入字符串進行匹配。
以第一階段為例,我們可以將每個項分為三種情況 數字、x^n、a*x^n。這里的n可以為任何值,甚至可以在等於1的時候直接省略。
那么這三種情況可以分別匹配兩個正則表達式:“[+-]?[0-9]+”,“([+-]?[0-9]?\\s*\\*\\s*)[x]\\s*(^\\s*[+-][0-9]?)?”,第二個正則表達式匹配后三種情況(了解更多關於正則表達式)
但是在第三階段中,由於嵌套的出現,除非在處理過程中做非常嚴格的限制將正則表達式分析的范圍進行縮減,否則正則表達式無法完成對函數嵌套的字符串處理。
比如對於以下文法:
A::=aAb|i
那么我們理所當然的寫正則表達式A="i",A="a"+A+"b"。在這兩條正則表達式的字符串匹配完了之后,我們發現其實A里面並不包含着嵌套的內容,它僅僅只能匹配"aib",而不能匹配“aaibb”。當然,這是一個非常簡單的例子,可以用其他的方法來解決這個問題。然而任務中的情況比這個要復雜的多,舉這個例子也只是為了闡明在這個階段不適合使用正則表達式罷了。
這個時候應該用上另外一項利器,詞法分析。在計算機編譯程序中,通常就是使用詞法分析對輸入程序進行處理。(了解更多關於詞法分析)
2、求導處理:
由於在前一個部分中,三個階段使用了兩種不同的方法對字符串進行處理,在這個部分依舊分開講這個問題。
在1-2次任務中,形式和所需存儲的內容還是相對單一的,第一次任務每個項可以寫成 a*x^b 的形式,也就是說我們可以只存儲a和b 即可。而第二次任務中,每個項可以寫成“a*x^b*sin(x)^c*cos(x)^d”的形式。那么我們需要存儲a,b,c,d即可。我們只需要一次取一項,然后提取出對應的參數,然后按照規則求導,然后將參數組返回,這樣可以完成求導。
在第三次任務中,形式變得相當的復雜,三角函數中可以塞入項,函數相互嵌套等情況,從而讓我們不能用有限的參數表示一個項。上面的方法已經不適用了。
所以在求導時只能做這樣的處理:我們按求導類型,分為expression,item,factor。expression可以由多個item加減獲得,每個item可以由factor相乘獲得。每個factor由三角函數,冪函數,常數,或者嵌套函數(函數+factor)組成。每一個類別都向上一層傳遞自己讀了字符串的什么內容,自己對這段內容的求導結果是什么。每一層在接受自己的下一層傳輸的信息的同時,也要對信息按照規則進行整合。在最底層的factor中對sin、cos、冪函數應該有本質性的處理方式(比如sin->cos之類的)。
二、程序分析
(1)基於度量來分析自己的程序結構
廢話不多說,直接上三次作業的度量數據以及類圖。
①OO度量數據(使用插件MetricsReloaded)
重要符號意義說明:
- ev(G)基本復雜度是用來衡量程序非結構化程度的.
- Iv(G)模塊設計復雜度是用來衡量模塊判定結構,即模塊和其他模塊的調用關系。
- v(G)是用來衡量一個模塊判定結構的復雜程度,數量上表現為獨立路徑的條數。
- LOC: Line of Code
- NCLOC:Non-Commented Line Of Code
P1
LOC | NCLOC | |
Entry | 90 | 85 |
Main | 10 | 5 |
PolyDerivate | 239 | 217 |
P2
LOC | NCLOC | |
Entry | 96 | 86 |
Main | 10 | 5 |
Poly | 330 | 288 |
Term | 156 | 146 |
P3
LOC | NCLOC | |
Compact | 14 | 11 |
Entry | 28 | 24 |
Expression | 32 | 32 |
Factor | 181 | 170 |
Filter | 86 | 77 |
Item |
115 | 108 |
Main | 14 | 14 |
Reader | 88 | 83 |
②類圖(使用工具是intellij(旗艦版)自帶的diagram)
P1
P2
P3
缺點:其實很容易看出來,每一次作業的后半部分都有非常大的改動,主要是自己的程序並沒有考慮那么復雜的應用,也就是需要什么就寫什么。在后面的任務中,幾乎要全部重構。
優點:在最前面,Main后面一直是調用Entry。這里的Entry是用與放置不同的使用模式(release,debug,batch_test)。在進行測試的時候大大的方便了自己。
(2)分析自己程序的bug
說實話,自己寫出了不少的bug,主要的原因是沒有進行足夠嚴格的測試。而且在一些細節問題上沒有想清楚導致出現一些小錯誤,e.g.正負號寫反,沒有考慮0之類的。
我總結了一下,我犯的錯誤很多都是在細節實現時反復更改實現方式,從而導致在更改實現方式時,另外一部分的代碼的處理結果與另一部分代碼需要的函數輸入不匹配,導致bug出現。
(3)分析自己發現別人程序bug所采用的策略
雖然我沒有參加互測,但是我還是想聊一聊bug查找的一些bug的方法(白盒和黑盒測試)。
白盒測試:是通過程序的源代碼進行測試而不使用用戶界面。這種類型的測試需要從代碼句法發現內部代碼在算法,溢出,路徑,條件等等中的缺點或者錯誤,進而加以修正。
這個需要你去逐行閱讀代碼,同時,要嘗試設計測試樣例去覆蓋程序中的所有的分支。而且你也可以順便檢查一下代碼邏輯。
黑盒測試:是通過使用整個軟件或某種軟件功能來嚴格地測試, 而並沒有通過檢查程序的源代碼或者很清楚地了解該軟件的源代碼程序具體是怎樣設計的。
直白來說就是,就是知道已有的需求限制,划分等價類進行測試的方法。e.g. 如果只允許輸入0-100的數字,那么我么可以划分為以下等價類:非法字符輸入;<0; >100; 0-50; 50-100 共5個類型進行測試。對於具體的問題需要具體分析。這可以在宏觀層面上發現迅速發現bug,而不需要閱讀任何代碼。
(4)Applying Creational Pattern
在我的觀點看來,助教第一、第二次的目的達到了:讓我們習慣面向對象的方法和面向對象的程序編寫。
但是第三次的題目,目的應該沒有達到:使用繼承和接口。在這次作業中,更加核心的東西應該是(文法分析,單例化等)。3個類之間除了2個private變量名和3個函數名相同之外,幾乎沒有什么共同之處。無論是解析、求導、化簡都不一樣,在這次作業中繼承和借口的使用的急迫程度依舊不存在。