在上一篇中介紹了一門程序設(shè)計(jì)語言必須具備的一些特性,以及Scheme語言的基本語法。這一篇用上一篇提到的平方根的問題來看看一個(gè)問題是如何被逐步分解并解決的。我們首先看下平方根的數(shù)學(xué)定義:
上面的數(shù)學(xué)公式描述了一個(gè)數(shù)的平方根所具備的性質(zhì),但并沒有告訴我們應(yīng)該如何去求一個(gè)數(shù)的平方根。這是數(shù)學(xué)公式和計(jì)算機(jī)程序的不同之處。對(duì)于同一個(gè)問題,數(shù)學(xué)公式關(guān)心的是該問題的解所具備的性質(zhì) (what is);而計(jì)算機(jī)程序則關(guān)心應(yīng)該如何去得到問題的解 (how to)。那么到底求一個(gè)數(shù)的平方根呢?我們這里使用牛頓提出的無窮逼近法,這個(gè)算法思路很簡單,先假定我們要求數(shù)y的平方根x:
- 為x設(shè)定一個(gè)初始值
- 檢查x^2是否等于y,若是,則返回x, 否則將x賦值為(x+(x/y))/2
- 重復(fù)執(zhí)行第2步直到獲得解。
簡書的markdown對(duì)數(shù)學(xué)公式支持的不好,請(qǐng)見諒。有了上面的描述,我們就可以很快寫出代碼了,下面我就試試使用Scheme語言來實(shí)現(xiàn)。這里還是要扯點(diǎn)題外話,我看SCIP這本書并不是為了學(xué)習(xí)Scheme語言,而是學(xué)習(xí)書中分析問題和將抽象問題的方法,學(xué)習(xí)了這些之后,你會(huì)恍然發(fā)現(xiàn)現(xiàn)在大多數(shù)語言或者工具的一些特性在這本書中都講到了,比如python語言、guava庫、Java8主打的lambda表達(dá)式,stream等。編程語言的發(fā)展有兩條截然不同的路,一條是為了適應(yīng)計(jì)算機(jī)底層硬件或者說計(jì)算機(jī)體系結(jié)構(gòu)發(fā)展起來的,最具代表性的就是C語言;另一條路則是關(guān)心計(jì)算的本質(zhì)(比較抽象,這里僅是個(gè)人觀點(diǎn)),主要的代表就是Lisp語言,而我們這里的談到的Scheme就是Lisp語言的一種變體。但隨著技術(shù)的發(fā)展,這兩種不同類型的語言有點(diǎn)融合的趨勢(shì)。
在寫代碼前,我們首先分析下這個(gè)問題,在上一篇中我們說過,要解決一個(gè)問題時(shí),我們應(yīng)該將問題進(jìn)行分解,得到多個(gè)子問題,當(dāng)子問題解決后,我們將子問題的解組合就得到了原問題的解。通過算法的描述我們可以將原有的問題分解成一些子問題:判斷數(shù)x是不是問題的解;使用算法描述中的方法將x加以改進(jìn),每次對(duì)x的改進(jìn)都能夠更加接近問題的解,這就是無窮逼近。我們用Scheme語言翻譯下就是這樣:
(define (sqrt-iter x y)
(if (good-enough? x y)
x
(sqrt-iter (imporve x y) y)))
這里引入了函數(shù)sqrt-iter,它接收兩個(gè)參數(shù)x和y,x表示對(duì)y的平方根的猜想,通過遞歸調(diào)用(在Scheme語言里十分重要)得到解。在sqrt-iter里又引入了兩個(gè)函數(shù):good-enough?和imporve,對(duì)應(yīng)著我們分析的兩個(gè)子問題。而good-enough?應(yīng)該如何定義的呢?它接收兩個(gè)參數(shù)x和y,判斷x^2是否等于y。這個(gè)問題是一個(gè)比較經(jīng)典的面試題:判斷兩個(gè)浮點(diǎn)數(shù)是否相等。
(define (good-enough? x y)
(< (abs (- (square x) y)) 0.001))
(define (square x) (* x x))
(define (abs x)
(if (> x 0) x (- x)))
improve函數(shù)我們可以根據(jù)算法描述得到:
(define (improve x y)
(average x (/ x y)))
(define (average x y)
(/ (+ x y) 2))
這些子問題解決后,原問題就迎刃而解了:
(define (sqrt x)
(sqrt-iter 1.0 x))
這里我們假定任何數(shù)的平方根的初始值為1.0。現(xiàn)在我們?cè)賮砜聪聅qrt函數(shù),我們可以得到下面這張圖:
我們?cè)谔幚碓瓎栴}(sqrt)的時(shí)候,我們只需關(guān)心抽象的各個(gè)子問題,而每個(gè)子問題又可以分解為更多的子問題,各個(gè)子問題可以看做是一個(gè)個(gè)的黑匣子,我們無需關(guān)心起內(nèi)部實(shí)現(xiàn)的細(xì)節(jié),我們關(guān)心其提供的功能就夠了。其實(shí)任何的程序設(shè)計(jì)都可以通過這種手段去將問題逐步分解,這里的分解還需要注意一個(gè)問題,就是每個(gè)被分解后的子問題應(yīng)該遵循單一職責(zé)的原理,只有這樣,解決該子問題的方法才有可能被其他的模塊進(jìn)行復(fù)用。就像搭積木的例子里,我們應(yīng)該去組合一些通用形狀的積木。
好了,這一篇就簡單介紹了分析問題的方法。有疑問?請(qǐng)留言。另外課后的作業(yè)有一道題是之前搜狗公司的面試題,讀者可以思考下:
有inc函數(shù)和dec函數(shù),inc函數(shù)的作用是將輸入的參數(shù)加1后返回,dec函數(shù)的作用是將輸入的參數(shù)減1后返回,利用inc函數(shù)和dec函數(shù)定義加法函數(shù)。
(define (+ a b) (...))