用Scala解Hanoi塔
Posted on 2009-11-23 19:20 laogao 閱讀(2618) 評(píng)論(3) 編輯 收藏 所屬分類: On Java 、Programming in General 、Other Languages到處吹噓Scala很長(zhǎng)時(shí)間了,卻一直沒有在自己的blog上增加過任何相關(guān)內(nèi)容,今天就來(lái)補(bǔ)一補(bǔ)。當(dāng)然,Scala的基本特性就不多廢話了,網(wǎng)上已經(jīng)有相當(dāng)多的資料,如果懶得google,只想看完本文,那么你只需要知道它是一門以JVM為目標(biāo)運(yùn)行環(huán)境的靜態(tài)編程語(yǔ)言(當(dāng)然,Scala官方也有.NET版,但已不是其重點(diǎn)),同時(shí)具備面向?qū)ο蠛秃瘮?shù)式編程語(yǔ)言的特點(diǎn)。本文將通過一個(gè)簡(jiǎn)單的示例,展示Scala的FP能力,其中十分heavy的用到了implicit隱式轉(zhuǎn)換和模式匹配。
代碼是從guithub的gist上抄的,簡(jiǎn)單改了改,原始代碼見: http://gist.github.com/66925
def simpleName(m:Manifest[_]):String = {
val name = m.toString
name.substring(name.lastIndexOf('$')+1)
}
trait Num
final class Succ[Pre<:Num] extends Num
final class _1 extends Num
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
case class Move[N<:Num,A,B,C]()
implicit def move1[A,B,C](implicit a:Manifest[A], b:Manifest[B]) : Move[_1,A,B,C] = {
System.out.println("Move from " + simpleName(a) + " to " + simpleName(b))
null
}
implicit def moveN[P<:Num,A,B,C](implicit m1:Move[P,A,C,B], m2:Move[_1,A,B,C], m3:Move[P,C,B,A]) : Move[Succ[P],A,B,C] = null
def run[N<:Num,A,B,C](implicit m:Move[N,A,B,C]) = null
case class Left()
case class Center()
case class Right()
run[_3,Left,Right,Center]
這段代碼解決的是經(jīng)典的漢諾塔問題,通過一根中柱,將左柱上64個(gè)大小依次疊加的圓盤全部移動(dòng)到右柱,要求整個(gè)過程中每次只能移動(dòng)一個(gè)圓盤,且每個(gè)圓盤只能獨(dú)立占據(jù)一根柱子或是疊加在比自身更大的圓盤上。
簡(jiǎn)單分析一下就知道,這是一個(gè)遞歸問題(FP的英雄特長(zhǎng)):
- 當(dāng)只有1個(gè)圓盤時(shí),不用通過中柱,直接可以從左柱移動(dòng)到右柱;
- 當(dāng)有2個(gè)圓盤時(shí),將小盤移動(dòng)到中柱,剩下的大盤移動(dòng)到右柱,再?gòu)闹兄研”P移動(dòng)到右柱;
- 當(dāng)有3個(gè)圓盤時(shí),先移動(dòng)2個(gè)圓盤到中柱,再移動(dòng)大盤到右柱,再移動(dòng)2個(gè)圓盤到右柱;
- ...
很容易發(fā)現(xiàn)一個(gè)pattern,那就是移動(dòng)N(N>1)個(gè)圓盤,可以通過以下三個(gè)步驟:
- 移動(dòng)N-1個(gè)圓盤,從左柱到中柱;
- 移動(dòng)剩下的1個(gè)圓盤,從左柱到右柱;
- 移動(dòng)N-1個(gè)圓盤,從中柱到右柱。
在解釋代碼之前,先說說Scala的implicit隱式轉(zhuǎn)換,這是一個(gè)非常powerful的idea,當(dāng)Scala編譯器發(fā)現(xiàn)類型不匹配,它不會(huì)直接fail,而是嘗試從代碼中指定的,在scope內(nèi)的implicit轉(zhuǎn)換定義,來(lái)替換問題對(duì)象或表達(dá)式以滿足類型要求,當(dāng)然,為了避免歧義,同一時(shí)刻Scala需要找到唯一的一個(gè)滿足條件的implicit定義。
我們的代碼首先定義了一個(gè)取得友好類名的方法,不去深究它,然后定義了一個(gè)正整數(shù)的序列,也不去深究它了,你只需要當(dāng)作他們是正整數(shù)就好,接觸過FP的同學(xué)應(yīng)該對(duì)此類定義不陌生,接下來(lái)定義了如下3個(gè)支持implicit傳入?yún)?shù)的方法:
- implicit def move1[A,B,C](implicit a:Manifest[A], b:Manifest[B]) : Move[_1,A,B,C]
- implicit def moveN[P<:Num,A,B,C]( implicit
m1:Move[P,A,C,B],
m2:Move[_1,A,B,C],
m3:Move[P,C,B,A]
) : Move[Succ[P],A,B,C] - def run[N<:Num,A,B,C](implicit m:Move[N,A,B,C])
含義分別是:
- 當(dāng)需要Move[_1,A,B,C]值時(shí),可以找我?guī)兔?我有個(gè)side-effect,那就是輸出移動(dòng)指令);
- 當(dāng)需要Move[Succ[P],A,B,C]時(shí),可以找我?guī)兔Γ?/li>
- 運(yùn)行我,我接受Move[N,A,B,C]類型的參數(shù)。
簡(jiǎn)單說明:Scala用[]表示類型參數(shù),區(qū)別于Java的<>,另外,Scala的類型聲明在變量/函數(shù)定義之后。Move[_,A,B,C]的含義是通過C,從A移動(dòng)圓盤到B。
我們來(lái)模擬運(yùn)行一下,為了演示效果,用一個(gè)中等復(fù)雜的數(shù)目,3個(gè)圓盤,從Left移動(dòng)到Right。
run[_3,Left,Right,Center],對(duì)應(yīng)Move[Succ[_2],Left,Right,Center],于是展開成三個(gè)Move:
Move[_2,Left,Center,Right]即Move[Succ[_1],Left,Center,Right]
Move[_1,Left,Right,Center]
Move[_2,Center,Right,Left]即Move[Succ[_1],Center,Right,Left]
然后繼續(xù)展開Move[_2,Left,Center,Right]和Move[_2,Center,Right,Left],得到:
Move[_1,Left,Right,Center]
Move[_1,Left,Center,Right]
Move[_1,Right,Center,Left]
--------------------------
Move[_1,Left,Right,Center]
--------------------------
Move[_1,Center,Left,Right]
Move[_1,Center,Right,Left]
Move[_1,Left,Right,Center]
這個(gè)時(shí)候已經(jīng)全部都匹配Move[_1,A,B,C],于是我們很容易得到以下輸出:
Move from Left to Right
Move from Left to Center
Move from Right to Center
Move from Left to Right
Move from Center to Left
Move from Center to Right
Move from Left to Right
希望本文能帶給Scala初學(xué)者一些感性認(rèn)識(shí)和啟發(fā)。