【问题标题】:Recursion inside let functionlet函数内部的递归
【发布时间】:2012-10-08 22:23:50
【问题描述】:

我对 def 和 let 如何以不同方式绑定变量感到困惑。有人可以向我解释为什么会这样吗:

(def leven
  (memoize
   (fn [x y]
     (cond (empty? x) (count y)
           (empty? y) (count x)
           :else (min (+ (leven (rest x) y) 1)
                      (+ (leven x (rest y)) 1)
                      (+ (leven (rest x) (rest y)) (if (= (first x) (first y)) 0 1)))))))

但是当我尝试将函数声明为 let 时编译失败:

(def leven
  (let [l (memoize (fn [x y]
                     (cond (empty? x) (count y)
                           (empty? y) (count x)
                           :else (min (+ (l (rest x) y) 1)
                                      (+ (l x (rest y)) 1)
                                      (+ (l (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))]
    (l x y)))

编辑:这行得通,使用 Ankur 展示的技术。

(defn leven [x y]
  (let [l (memoize (fn [f x y]
                     (cond (empty? x) (count y)
                           (empty? y) (count x)
                           :else (min (+ (f f (rest x) y) 1)
                                      (+ (f f x (rest y)) 1)
                                      (+ (f f (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))
        magic (partial l l)]
    (magic x y)))

【问题讨论】:

    标签: recursion clojure let


    【解决方案1】:

    以下是执行您要求的示例。为了简单起见,我使用阶乘,并在阶乘中添加了 println 以确保记忆工作正常

    (let [fact (memoize (fn [f x] 
                           (println (str "Called for " x))
                           (if (<= x 1) 1 (* x  (f f (- x 1))))))
          magic (partial fact fact)] 
         (magic 10)
         (magic 11))
    

    首先计算 10 的阶乘,然后计算 11,在这种情况下,它不应该再次调用 10 到 1 的阶乘,因为已经记住了。

    Called for 10
    Called for 9
    Called for 8
    Called for 7
    Called for 6
    Called for 5
    Called for 4
    Called for 3
    Called for 2
    Called for 1
    Called for 11
    39916800
    

    【讨论】:

    • 非常有趣。所以你基本上只是将函数作为参数传入,这样编译器就不会因为它没有被定义而感到困惑。我现在无法尝试,但我稍后会尝试这种方法。
    【解决方案2】:

    let 表单按顺序绑定名称,因此在您的第二个函数定义中,名称 l 在您尝试引用它时不存在。您可以使用letfn(带有一些小修改)或给定义的函数一个名称,然后改为引用它,如下所示:

    (def leven  
      (let [l (memoize (fn SOME-NAME [x y]
        (cond 
          (empty? x) (count y)
          (empty? y) (count x)
          :else (min (+ (SOME-NAME (rest x) y) 1)
                     (+ (SOME-NAME x (rest y)) 1)
                     (+ (SOME-NAME (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))]
    l))
    

    您可能会注意到,我将 let 的返回值更改为 l 本身,因为这是您希望 leven 绑定的内容。 (l x y) 是有问题的,因为它只引用了函数本地的绑定,let 无法访问。

    【讨论】:

    • 这样使用 SOME-NAME 功能不会失去 memoize 的好处吗?难道你不需要调用函数memoize返回,或者在let语句中不可能有一个递归的memoized函数?
    • @onit 可以修改leven 的定义以获得记忆的好处,方法是将SOME-NAME 作为第一个参数:(fn [SOME-NAME x y],然后还将对SOME-NAME 的调用替换为(SOME-NAME SOME-NAME ...) 最后将返回值 l 替换为 (partial l l)
    猜你喜欢
    • 2021-07-19
    • 1970-01-01
    • 2015-05-09
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 2020-01-19
    相关资源
    最近更新 更多