<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
      
       彈簧床這個名稱很形象,一次調用就是落到彈簧床上,如果是函數,反彈回去再來一次,如果不是,本次表演結束。
       
     
    主站蜘蛛池模板: 日韩插啊免费视频在线观看| 美女黄频免费网站| 国产亚洲精品线观看动态图| 亚洲欧美国产国产综合一区| 国产成人精品免费视频大| 亚洲精品自产拍在线观看动漫| 欧洲人免费视频网站在线| 亚洲AV乱码久久精品蜜桃 | 亚洲精品国产日韩无码AV永久免费网| 亚洲色少妇熟女11p| 大学生一级特黄的免费大片视频| 亚洲一区二区三区国产精品| 国产国产人免费人成成免视频| 亚洲综合色成在线播放| 色播在线永久免费视频网站| 亚洲乱亚洲乱淫久久| 一二三四在线观看免费高清中文在线观看 | 久久精品国产亚洲AV麻豆网站 | 国产精品亚洲综合网站| 最近最好最新2019中文字幕免费| 亚洲精品福利网站| 一个人免费视频观看在线www| 亚洲成av人在线视| 日日麻批免费40分钟日本的| 亚洲精品456人成在线| 亚洲一区二区免费视频| 亚洲中文字幕一区精品自拍| 天堂亚洲免费视频| 亚洲中文字幕久久久一区| 四虎影视精品永久免费网站| 国产精品午夜免费观看网站| 久久精品国产精品亚洲蜜月| 国产精品久久久久久久久免费| 亚洲av无码一区二区三区人妖| 97在线观免费视频观看| 精品在线免费视频| 亚洲人成在线观看| 国产精品二区三区免费播放心| 亚洲欧美日韩国产精品一区| 亚洲综合色区在线观看| 成人浮力影院免费看|