我正在尝试编写一个在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。
答案 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
(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。