如何在Clojure中生成memoized递归函数?

时间:2010-10-11 13:51:01

标签: recursion clojure scope closures memoization

我正在尝试编写一个在Clojure中返回memoized递归函数的函数,但是我无法使递归函数看到它自己的memoized绑定。这是因为没有创建var吗?另外,为什么我不能在用let创建的本地绑定上使用memoize?

这个稍微不同寻常的Fibonacci序列制作者从一个特定的数字开始就是我希望我能做的一个例子:

(defn make-fibo [y]
  (memoize (fn fib [x] (if (< x 2)
             y
             (+ (fib (- x 1))
                (fib (- x 2)))))))

(let [f (make-fibo 1)]
  (f 35)) ;; SLOW, not actually memoized

使用with-local-vars似乎是正确的方法,但它对我也不起作用。我想我无法关闭变种?

(defn make-fibo [y]
  (with-local-vars [fib (fn [x] (if (< x 2)
                                  y
                                  (+ (@fib (- x 1))
                                     (@fib (- x 2)))))]
    (memoize fib)))

(let [f (make-fibo 1)]
  (f 35)) ;; Var null/null is unbound!?! 

我当然可以手动编写一个创建封闭原子的宏并自己管理memoization,但我希望这样做没有这样的hackery。

8 个答案:

答案 0 :(得分:19)

这似乎有效:

(defn make-fibo [y]
  (with-local-vars
      [fib (memoize
            (fn [x]
              (if (< x 2)
                y
                (+ (fib (- x 2)) (fib (dec x))))))]
    (.bindRoot fib @fib)
    @fib))

with-local-vars仅为新创建的Vars提供线程局部绑定,一旦执行离开with-local-vars表单就会弹出;因此需要.bindRoot

答案 1 :(得分:18)

(def fib (memoize (fn [x] (if (< x 2)
                              x
                              (+ (fib (- x 1))
                                 (fib (- x 2)))))))
(time (fib 35))

答案 2 :(得分:16)

有一种有趣的方法可以完全依赖重新绑定和def的行为。主要技巧是通过将函数作为参数传递给自身来绕过递归的限制:

(defn make-fibo [y]
  (let
    [fib
      (fn [mem-fib x]
         (let [fib (fn [a] (mem-fib mem-fib a))]
           (if (<= x 1)
             y
             (+ (fib (- x 1)) (fib (- x 2))))))
     mem-fib (memoize fib)]

     (partial mem-fib mem-fib)))

然后:

> ((make-fibo 1) 50)
20365011074

这里会发生什么:

  • fib递归函数获得了一个新参数mem-fib。一旦定义,这将是fib本身的备忘版本。
  • fib正文包裹在let形式中,重新定义对fib的调用,以便将mem-fib向下传递到下一级递归。
  • mem-fib定义为memoized fib
  • ...并将partial作为第一个参数传递给自己以启动上述机制。

这个技巧类似于Y组合器在没有内置递归机制的情况下计算函数的定点的技巧。

鉴于def“看到”正在定义的符号,除了创建匿名的就地递归memoized函数之外,没有什么实际的理由可以这样做。

答案 3 :(得分:3)

这是最简单的解决方案:

(def fibo
  (memoize (fn [n]
             (if (< n 2)
               n
               (+ (fibo (dec n))
                  (fibo (dec (dec n))))))))

答案 4 :(得分:2)

如果您计划多次使用它,可以将递归的memoized函数模式封装在宏中。

(defmacro defmemo
  [name & fdecl]
  `(def ~name
     (memoize (fn ~fdecl))))

答案 5 :(得分:0)

你的第一个版本确实有效,但你没有获得memoization的所有好处,因为你只运行一次算法。

试试这个:

user>  (time (let [f (make-fibo 1)]
          (f 35)))
"Elapsed time: 1317.64842 msecs"
14930352

user>  (time (let [f (make-fibo 1)]
          [(f 35) (f 35)]))
"Elapsed time: 1345.585041 msecs"
[14930352 14930352]

答案 6 :(得分:0)

这是Y-combinator和Clojure memoize之间的交叉:

(defn Y-mem [f]
  (let [mem (atom {})]
    (#(% %)
     (fn [x]
       (f #(if-let [e (find @mem %&)]
            (val e)
            (let [ret (apply (x x) %&)]
              (swap! mem assoc %& ret)
              ret))))))))

你可以对此进行macrosugar:

(defmacro defrecfn [name args & body]
  `(def ~name
       (Y-mem (fn [foo#]
                 (fn ~args (let [~name foo#] ~@body))))))

现在使用它:

(defrecfn fib [n]
  (if (<= n 1)
      n
      (+' (fib (- n 1))
          (fib (- n 2)))))

user=> (time (fib 200))
"Elapsed time: 0.839868 msecs"
280571172992510140037611932413038677189525N

Levenshtein distance

(defrecfn edit-dist [s1 s2]
  (cond (empty? s1) (count s2)
        (empty? s2) (count s1)
        :else (min (inc (edit-dist s1 (butlast s2)))
                   (inc (edit-dist (butlast s1) s2))
                   ((if (= (last s1) (last s2)) identity inc)
                      (edit-dist (butlast s1) (butlast s2))))))

答案 7 :(得分:0)

您可以使用Y组合器的变体在Clojure中生成memoized递归函数。例如,factorial的代码为:

(def Ywrap
  (fn [wrapper-func f]
    ((fn [x]
       (x x))
     (fn [x]
       (f (wrapper-func (fn [y]
                      ((x x) y))))))))

 (defn memo-wrapper-generator [] 
   (let [hist (atom {})]
    (fn [f]
      (fn [y]
        (if (find @hist y)
          (@hist y)
         (let [res (f y)]
           (swap! hist assoc y res)
        res))))))

(def Ymemo 
  (fn [f]
   (Ywrap (memo-wrapper-generator) f)))

(def factorial-gen
  (fn [func]
    (fn [n]
      (println n)
     (if (zero? n)
      1
      (* n (func (dec n)))))))

(def factorial-memo (Ymemo factorial-gen))

本文详细介绍了Y combinator real life application: recursive memoization in clojure