Read Sean

          Read me, read Sean.
          posts - 508, comments - 655, trackbacks - 9, articles - 4

          用Scala解Hanoi塔

          Posted on 2009-11-23 19:20 laogao 閱讀(2618) 評(píng)論(3)  編輯  收藏 所屬分類: On JavaProgramming in GeneralOther 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

          import scala.reflect.Manifest
            
          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è)步驟:

          1. 移動(dòng)N-1個(gè)圓盤,從左柱到中柱;
          2. 移動(dòng)剩下的1個(gè)圓盤,從左柱到右柱;
          3. 移動(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ù)的方法:

          1. implicit def move1[A,B,C](implicit a:Manifest[A], b:Manifest[B]) : Move[_1,A,B,C]
          2. 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]
          3. def run[N<:Num,A,B,C](implicit m:Move[N,A,B,C])

          含義分別是:

          1. 當(dāng)需要Move[_1,A,B,C]值時(shí),可以找我?guī)兔?我有個(gè)side-effect,那就是輸出移動(dòng)指令);
          2. 當(dāng)需要Move[Succ[P],A,B,C]時(shí),可以找我?guī)兔Γ?/li>
          3. 運(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ā)。


          Feedback

          # re: 用Scala解Hanoi塔  回復(fù)  更多評(píng)論   

          2009-11-24 16:28 by 大胃
          歡迎大家參與討論,我會(huì)不定期刪除自言自語(yǔ)式的、軟廣告式的、和主題無(wú)關(guān)的、沒有營(yíng)養(yǎng)的回復(fù)。

          # re: 用Scala解Hanoi塔  回復(fù)  更多評(píng)論   

          2010-06-11 16:18 by 大胃
          新發(fā)表一篇文章,歡迎前往討論
          http://laogao.me/blog/view/8

          # re: 用Scala解Hanoi塔  回復(fù)  更多評(píng)論   

          2012-05-18 23:14 by Freewind
          我謹(jǐn)代表群內(nèi)171位scalaer,熱切希望你加入到scala熱情交流群: 132569382(QQ群)
          主站蜘蛛池模板: 安图县| 建昌县| 新和县| 天长市| 开封县| 昭苏县| 闸北区| 乐昌市| 建平县| 望谟县| 新疆| 仲巴县| 原平市| 广汉市| 福泉市| 芒康县| 安顺市| 荆门市| 安阳县| 泰州市| 南和县| 泽库县| 南充市| 集安市| 安国市| 汝南县| 太仓市| 攀枝花市| 安庆市| 木里| 东至县| 西充县| 石泉县| 芮城县| 舒城县| 绵阳市| 富顺县| 化德县| 伊春市| 万安县| 泗洪县|