Recursion 如何在Clojure中生成记忆递归函数?
我试图在Clojure中编写一个返回记忆化递归函数的函数,但在使递归函数看到自己的记忆化绑定时遇到了困难。这是因为没有创建var吗?另外,为什么我不能在用let创建的本地绑定上使用memoize 这个有点不寻常的斐波那契序列生成器,从一个特定的数字开始,就是我希望能够做到的一个例子:Recursion 如何在Clojure中生成记忆递归函数?,recursion,clojure,scope,closures,memoization,Recursion,Clojure,Scope,Closures,Memoization,我试图在Clojure中编写一个返回记忆化递归函数的函数,但在使递归函数看到自己的记忆化绑定时遇到了困难。这是因为没有创建var吗?另外,为什么我不能在用let创建的本地绑定上使用memoize 这个有点不寻常的斐波那契序列生成器,从一个特定的数字开始,就是我希望能够做到的一个例子: (defn make-fibo [y] (memoize (fn fib [x] (if (< x 2) y (+ (fib (- x 1))
(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
当然,我可以手动编写一个宏,创建一个封闭的atom并自己管理备忘录,但我希望在没有这种黑客的情况下实现这一点 您的第一个版本实际上是可行的,但您并没有获得记忆的所有好处,因为您只运行了一次算法 试试这个:
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]
(def fib(记忆)(fn[x](如果(
这似乎有效:
(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))
(定义使fibo[y]
(与本地VAR合作)
[fib(记忆化
(fn[x]
(如果(
with local vars
仅为新创建的vars提供线程本地绑定,一旦执行离开with local vars
表单,就会弹出线程本地绑定;因此需要.bindRoot
如果您计划多次使用递归记忆函数模式,则可以将其封装在宏中
(defmacro defmemo
[name & fdecl]
`(def ~name
(memoize (fn ~fdecl))))
有一种有趣的方法,它既不依赖于重新绑定,也不依赖于
def
的行为。主要技巧是绕过递归的限制,将函数作为参数传递给自身:
(defn make-fibo [y]
(let
[fib
(fn [mem-fib x]
(let [fib (fn [a] (mem-fib mem-fib a))]
(if (<= x 2)
y
(+ (fib (- x 1)) (fib (- x 2))))))
mem-fib (memoize fib)]
(partial mem-fib mem-fib)))
这里发生了什么:
递归函数获得了一个新参数fib
。一旦定义了memfib
本身,这将是它的记忆版本fib
主体被包装在一个fib
表单中,该表单重新定义对let
的调用,以便它们将fib
传递到下一个递归级别memfib
定义为记忆mem-fib
fib
- 。。。并将通过
作为启动上述机制的第一个参数传递给自身partial
鉴于
def
“看到”正在定义的符号,没有什么实际理由这样做,除了创建匿名就地递归记忆函数之外。以下是最简单的解决方案:
(def fibo
(memoize (fn [n]
(if (< n 2)
n
(+ (fibo (dec n))
(fibo (dec (dec n))))))))
(def fibo
(备忘录化(fn[n]
(如果(
这是与Clojure的交叉:
您可以使用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
您可以使用Y组合器的变体在Clojure中生成记忆递归函数。例如,
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))
本文详细解释了这一点。不过,它不是递归工作的,这远比缓存单个结束值重要。丁丁,谢谢,我们有一个赢家!但是为什么我们必须跳入javaland来做bindRoot呢?更重要的是,如果两个线程几乎同时执行一个.bindRoot,那么在vars退出此函数的作用域时关闭之前,这难道不会造成并发风险吗?对于生成的斐波那契函数的并发创建,这仍然安全吗?或者.bindRoot是否以某种方式在词汇上确定了作用域?我仍然有点困惑…
.bindRoot
是同步的
,但是这在这里并不重要,因为我们在一个本地变量上调用它,而这个本地变量此时无法从任何其他线程访问。至于方法调用的Javaish风格,我认为这在这里是不可避免的(alter-var-root
将不起作用,因为它要求一些根绑定已经到位),但我不认为这是一个问题。如果有什么不同的话,我想知道我是否更愿意以不涉及本地VAR的方式做同样的事情,但另一方面,这似乎是一个特别简单的方法…谢谢,我想我现在明白了。bindRoot调用创建了var的根绑定,但是该绑定不与其他线程共享,因为它们有自己的var线程本地绑定,因此var的动态作用域不会影响我们。此外,bindRoot并不意味着var将从顶层可见。根绑定可以通过memoize
后面的机制从其他线程访问,但是后者是线程安全的。(但有关Clojure和相关gotchas中的回忆录的深入分析,请参见Meikel Brandmeyer)。然而,除了带有局部变量的表单的主体(它是该主体的局部变量)之外,从任何地方都无法直接看到Var因此,在make fibo
返回后,除了通过调用返回的函数外,无法以任何方式获取。对于将来的读者。。。我将其提取到一个宏中:如果您希望在名称空间中绑定var,那么这是更典型的样式,但不幸的是您错误地更改了函数!y参数怎么了<代码>(fib 2000)
给出堆栈溢出错误。上面的示例不使用recur,因此堆栈溢出是不可避免的,除非通过调用函数1到2000“预热”备忘录。但是你怎么知道2000对于一个任意的用例来说是足够大的呢?这就是问题所在!由@Phelix和@Carlosnues给出的解决方案在。
(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))))))))
(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))))))
(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))