<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 閱讀(3418) 評論(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
      
       彈簧床這個名稱很形象,一次調用就是落到彈簧床上,如果是函數,反彈回去再來一次,如果不是,本次表演結束。
       
     
    主站蜘蛛池模板: jjizz全部免费看片| 亚洲人成电影在线观看青青| 亚洲av永久无码精品秋霞电影秋 | 亚洲视频在线播放| 99久久成人国产精品免费| 亚洲精品一级无码中文字幕| 一级毛片正片免费视频手机看| 国产区卡一卡二卡三乱码免费| 亚洲一区欧洲一区| 国产精品jizz在线观看免费| 成人亚洲国产精品久久| 全部免费a级毛片| 好猛好深好爽好硬免费视频| 亚洲精品无码永久在线观看你懂的| a级毛片100部免费观看| 亚洲一区免费观看| 啦啦啦高清视频在线观看免费| 精品亚洲成a人在线观看| 免费一级特黄特色大片在线| 中文字幕在线免费看| 亚洲AV人无码综合在线观看| 免费下载成人电影| 国产亚洲美女精品久久久久| 国产精品亚洲αv天堂无码| 欧洲人成在线免费| 亚洲午夜精品国产电影在线观看| 日韩视频免费在线| 好男人资源在线WWW免费| 91精品国产亚洲爽啪在线影院 | 日本高清免费中文字幕不卡| 黄色网址大全免费| 亚洲ⅴ国产v天堂a无码二区| 免费精品人在线二线三线区别| 国产成人精品久久亚洲高清不卡 | 亚洲国产精品激情在线观看| a视频在线免费观看| 亚洲偷偷自拍高清| 亚洲无人区午夜福利码高清完整版| 97在线视频免费播放| 亚洲av色香蕉一区二区三区蜜桃| 久久亚洲精品视频|