Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion 如何在Clojure中生成记忆递归函数?_Recursion_Clojure_Scope_Closures_Memoization - Fatal编程技术网

Recursion 如何在Clojure中生成记忆递归函数?

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))

我试图在Clojure中编写一个返回记忆化递归函数的函数,但在使递归函数看到自己的记忆化绑定时遇到了困难。这是因为没有创建var吗?另外,为什么我不能在用let创建的本地绑定上使用memoize

这个有点不寻常的斐波那契序列生成器,从一个特定的数字开始,就是我希望能够做到的一个例子:

(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
    作为启动上述机制的第一个参数传递给自身
这个技巧类似于Y组合器在没有内置递归机制的情况下计算函数的固定点时使用的技巧


鉴于
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))