<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

    詳解Clojure的遞歸(下)——相互遞歸和trampoline

    Posted on 2010-08-22 13:52 dennis 閱讀(3412) 評論(0)  編輯  收藏 所屬分類: Clojure
        詳解clojure遞歸(上)
        詳解clojure遞歸(下)
       
        這篇blog拖到現在才寫,如果再不寫,估計就只有上篇沒有下篇了,趁周末寫一下。

        上篇提到了Clojure僅支持有限的TCO,不支持間接的TCO,但是有一類特殊的尾遞歸clojure是支持,這就是相互遞歸。且看一個例子,定義兩個函數用于判斷奇數偶數:
    (declare my-odd? my-even?)
    (defn my
    -odd? [n]
          (
    if (= n 0)
              false
             (my
    -even? (dec n))))
    (defn my
    -even? [n]
          (
    if (= n 0)
              true
             (my
    -odd? (dec n))))

        這里使用declare做前向聲明,不然在定義my-odd?的時候my-even?還沒有定義,導致出錯。可以看到,my-odd?調用了my-even?,而my-even?調用了my-odd?,這是一個相互遞歸的定義:如果n為0,那肯定不是奇數,否則就判斷n-1是不是偶數,反之亦然。

        這個遞歸定義看起來巧妙,但是剛才已經提到clojure通過recur支持直接的TCO優化,這個相互遞歸在大數計算上會導致堆棧溢出:
    user=> (my-odd? 10000)
    java.lang.StackOverflowError (NO_SOURCE_FILE:
    0)

    user=> (my-even? 10000)
    java.lang.StackOverflowError (NO_SOURCE_FILE:0)


        怎么解決呢?一個辦法是轉化成一個直接遞歸調用,定義一個parity函數,當偶數的時候返回0,當奇數的時候返回1:
    (defn parity [n]
          (loop [n n par 
    0]
                (
    if (= n 0)
                    par
                    (recur (dec n) (
    - 1 par)))))
    user=> (parity 3)
    1
    user=> (parity 100)
    0
    user=> (parity 10000)
    0
    user=> (parity 10001)
    1
       

        parity是直接的尾遞歸,并且使用了recur優化,重新定義my-odd和my-even就很簡單了:
    (defn my-even? [n] (= 0 (parity n)))
    (defn my
    -odd?  [n] (= 1 (parity n)))
       
        但是這樣的方式終究不是特別優雅,相互遞歸的定義簡潔優雅,有沒有什么辦法保持這個優雅的定義并且避免堆棧溢出呢?答案是trampoline,翻譯過來是彈簧床,查看trampoline函數文檔:
    user=> (doc trampoline)
    -------------------------
    clojure.core
    /trampoline
    ([f] [f 
    & args])
      trampoline can be used to convert algorithms requiring mutual
      recursion without stack consumption. Calls f with supplied args, 
    if
      any. If f returns a fn, calls that fn with no arguments, and
      continues to repeat, until the 
    return value is not a fn, then
      returns that non
    -fn value. Note that if you want to return a fn as a
      
    final value, you must wrap it in some data structure and unpack it
      after trampoline returns.
       簡單來說trampoline接收一個函數做參數并調用,如果結果為函數,那么就遞歸調用函數,如果不是,則返回結果,主要就是為了解決相互遞歸調用堆棧溢出的問題,查看trampoline的定義:
    (defn trampoline
      ([f]
         (let [ret (f)]
           (
    if (fn? ret)
              (recur ret)
              ret)))

       看到trampoline利用recur做了尾遞歸優化,原有的my-odd?和my-even?需要做一個小改動,就可以利用trampoline來做遞歸優化了:
    (declare my-odd? my-even?)
    (defn my
    -odd? [n]
          (
    if (= n 0)
              
    false
             #(my
    -even? (dec n))))
    (defn my
    -even? [n]
          (
    if (= n 0)
              
    true
             #(my
    -odd? (dec n))))
     
       唯一的改動就是函數的末尾行前面加了個#,將遞歸調用修改成返回一個匿名函數了,使用trampoline調用my-even?和my-odd?不會再堆棧溢出了:
    user=> (trampoline my-odd? 10000000)
    false
    user
    => (trampoline my-even? 10000)  
    true
    user
    => (trampoline my-even? (* 1000 1000))
    true
      
       彈簧床這個名稱很形象,一次調用就是落到彈簧床上,如果是函數,反彈回去再來一次,如果不是,本次表演結束。
       
     
    主站蜘蛛池模板: 国产一区视频在线免费观看| 久久久久高潮毛片免费全部播放| 天天干在线免费视频| 亚洲天堂中文字幕在线观看| 2015日韩永久免费视频播放| 中文字幕亚洲免费无线观看日本| 91青青国产在线观看免费| 亚洲影院在线观看| 日本妇人成熟免费中文字幕| 亚洲香蕉久久一区二区| 成年性生交大片免费看| 亚洲第一se情网站| 4338×亚洲全国最大色成网站| 一级特黄录像视频免费| 亚洲欧洲日产国码无码久久99| 免费一级不卡毛片| 亚洲国产美女在线观看| 日韩免费观看的一级毛片| 免费无码AV一区二区| 亚洲精品无码mv在线观看网站 | 成人黄色免费网站| 亚洲一卡二卡三卡| 精品国产一区二区三区免费看| 国产成人精品日本亚洲语音 | 国产免费爽爽视频免费可以看| 思思久久99热免费精品6| 久久精品亚洲视频| 少妇高潮太爽了在线观看免费| 国产成人亚洲精品无码AV大片| 亚洲人成人77777网站| 最新黄色免费网站| 综合一区自拍亚洲综合图区| 国产L精品国产亚洲区久久 | 久久久国产精品亚洲一区| 国产精品免费观看久久| 人妻免费久久久久久久了| 亚洲综合色丁香麻豆| 国产成人免费一区二区三区| 污视频在线观看免费| 亚洲Aⅴ在线无码播放毛片一线天 亚洲avav天堂av在线网毛片 | 免费在线精品视频|