Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/webpack/2.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
如何在Lisp中记忆递归函数?_Lisp_Common Lisp_Memoization - Fatal编程技术网

如何在Lisp中记忆递归函数?

如何在Lisp中记忆递归函数?,lisp,common-lisp,memoization,Lisp,Common Lisp,Memoization,我是口齿不清的初学者。我试图记忆一个递归函数,用于计算a中的项数(对于中的问题14)。到目前为止,我的代码是: (defun collatz-steps (n) (if (= 1 n) 0 (if (evenp n) (1+ (collatz-steps (/ n 2))) (1+ (collatz-steps (1+ (* 3 n))))))) (defun p14 () (defvar m-collatz-steps (m

我是口齿不清的初学者。我试图记忆一个递归函数,用于计算a中的项数(对于中的问题14)。到目前为止,我的代码是:

(defun collatz-steps (n)
  (if (= 1 n) 0
       (if (evenp n) 
           (1+ (collatz-steps (/ n 2)))
           (1+ (collatz-steps (1+ (* 3 n)))))))

(defun p14 ()
  (defvar m-collatz-steps (memoize #'collatz-steps))
  (let 
      ((maxsteps (funcall m-collatz-steps 2))
       (n 2)
       (steps))
    (loop for i from 1 to 1000000
          do 
          (setq steps (funcall m-collatz-steps i))
          (cond 
            ((> steps maxsteps) 
             (setq maxsteps steps)
             (setq n i))
            (t ())))
    n))


(defun memoize (fn)
  (let ((cache (make-hash-table :test #'equal)))
    #'(lambda (&rest args)
        (multiple-value-bind 
              (result exists)
            (gethash args cache)
          (if exists
              result
              (setf (gethash args cache)
                    (apply fn args)))))))
memoize函数与本书中给出的函数相同

与非记忆版本相比,此代码实际上没有提供任何加速。我相信这是因为递归调用调用了函数的非记忆版本,这有点违背了它的目的。在这种情况下,什么是正确的方法来做这里的备忘录?有没有办法让对原始函数的所有调用调用调用记忆版本本身,从而消除对特殊m-collatz-steps符号的需要

编辑:将代码更正为

(defvar m-collatz-steps (memoize #'collatz-steps))
这就是我代码中的内容。 在编辑之前,我错误地提出:

(defvar collatz-steps (memoize #'collatz-steps))
看到这个错误给了我另一个想法,我尝试使用最后一个defvar本身并将递归调用更改为

       (1+ (funcall collatz-steps (/ n 2)))
       (1+ (funcall collatz-steps (1+ (* 3 n))))
这似乎确实执行了记忆功能(从大约60秒加速到1.5秒),但需要更改原始功能。是否有一种不涉及改变原始功能的更清洁的解决方案

类似这样:

(setf collatz-steps (memoize lambda (n)
  (if (= 1 n) 0
    (if (evenp n) 
        (1+ (collatz-steps (/ n 2)))
        (1+ (collatz-steps (1+ (* 3 n))))))))
(defun collatz-steps (n)
  (if (= 1 n) 0
      (if (evenp n) 
          (1+ (collatz-steps (/ n 2)))
          (1+ (collatz-steps (1+ (* 3 n)))))))

(memoize-function 'collatz-steps)

IOW:您的原始(未记忆的)函数是匿名的,您只为记忆的结果命名。

我假设您使用的是Common Lisp,它对变量和函数名有单独的名称空间。为了记忆由符号命名的函数,需要通过访问器“fdefinition”更改其函数绑定:

(setf (fdefinition 'collatz-steps) (memoize #'collatz-steps))

(defun p14 ()
  (let ((mx 0) (my 0))
    (loop for x from 1 to 1000000
          for y = (collatz-steps x)
          when (< my y) do (setf my y mx x))
    mx))
(setf(定义“collatz步骤”(记忆“collatz-steps))
(第14页()
(let((mx 0)(my 0))
(从1到1000000的x循环
对于y=(缩进步骤x)
当(
这是一个重新绑定符号功能的记忆功能:

(defun memoize-function (function-name)
  (setf (symbol-function function-name)
    (let ((cache (make-hash-table :test #'equal)))
         #'(lambda (&rest args)
             (multiple-value-bind 
                 (result exists)
                (gethash args cache)
               (if exists
                   result
                   (setf (gethash args cache)
                         (apply fn args)))))))
然后,您将执行以下操作:

(setf collatz-steps (memoize lambda (n)
  (if (= 1 n) 0
    (if (evenp n) 
        (1+ (collatz-steps (/ n 2)))
        (1+ (collatz-steps (1+ (* 3 n))))))))
(defun collatz-steps (n)
  (if (= 1 n) 0
      (if (evenp n) 
          (1+ (collatz-steps (/ n 2)))
          (1+ (collatz-steps (1+ (* 3 n)))))))

(memoize-function 'collatz-steps)
我将让您自行创建一个未记忆的函数。

更改“原始”函数是必要的,因为正如您所说,没有其他方法可以更新递归调用以调用已记忆的版本

幸运的是,lisp的工作方式是在每次需要调用函数时按名称查找函数。这意味着用函数的记忆版本替换函数绑定就足够了,这样递归调用将自动查找并通过记忆重新输入

怀远的代码显示了关键步骤:

(setf (fdefinition 'collatz-steps) (memoize #'collatz-steps))
这个技巧在Perl中也有效。然而,在像C这样的语言中,函数的记忆版本必须单独编码


一些lisp实现提供了一个名为“advice”的系统,该系统提供了一个标准化的结构,用于用增强版本的函数替换函数。除了功能升级(如记忆)外,通过插入调试打印(或完全停止并给出持续提示),这在调试中非常有用不修改原始代码。

不久前,我为Scheme编写了一个小的记忆例程,它使用一系列闭包来跟踪记忆状态:

(define (memoize op)
  (letrec ((get (lambda (key) (list #f)))
           (set (lambda (key item)
                  (let ((old-get get))
                    (set! get (lambda (new-key)
                                (if (equal? key new-key) (cons #t item)
                                    (old-get new-key))))))))
    (lambda args
      (let ((ans (get args)))
        (if (car ans) (cdr ans)
            (let ((new-ans (apply op args)))
              (set args new-ans)
              new-ans))))))
这需要像这样使用:

(define fib (memoize (lambda (x)
                       (if (< x 2) x
                           (+ (fib (- x 1)) (fib (- x 2)))))))
(定义fib(记忆)(λ(x)
(如果(

我确信这可以很容易地移植到您最喜欢的词汇范围的Lisp风格。

我可能会这样做:

(let ((memo (make-hash-table :test #'equal)))
  (defun collatz-steps (n)
    (or (gethash n memo)
    (setf (gethash n memo)
          (cond ((= n 1) 0)
            ((oddp n) (1+ (collatz-steps (+ 1 n n n))))
            (t (1+ (collatz-steps (/ n 2)))))))))
它既不美观也不实用,但是,它没有太多麻烦,而且确实有效。缺点是,您没有一个方便的未加密版本可供测试,清除缓存几乎是“非常困难的”。

请注意以下几点:

(defun foo (bar)
   ... (foo 3) ...)
上面是一个具有自身调用的函数

在公共Lisp中,文件编译器可以假定FOO没有改变。它以后不会调用更新的FOO。如果更改FOO的函数绑定,则原始函数的调用仍将转到旧函数

因此,在一般情况下,记忆自递归函数将不起作用。尤其是如果您使用的是好的编译器。

您可以绕过它始终遍历符号,例如:(funcall'foo3)

(DEFVAR…)是顶级表单。不要在函数内部使用它。如果您声明了一个变量,请稍后使用SETQ或SETF进行设置


对于您的问题,我只使用哈希表来存储中间结果。

此函数正是Peter Norvig作为函数示例给出的函数,它似乎是一个很好的记忆候选函数,但事实并非如此

参见他关于记忆化的原始论文中的图3(“使用自动记忆作为真实世界AI系统中的软件工程工具”)


所以我猜,即使你让记忆机制工作起来,在这种情况下也不会真正加快速度。

是的,这让它更清晰,但我认为你应该使用defun宏:(defun collatz steps(n)(memoize#’(lambda(x)等…n))如果在函数定义之后调用setf,这将起作用。No,Common Lisp不会每次都按名称找到函数。好的编译器会生成直接调用函数的代码。ANSI CL标准中也描述了这一点。问题:递归调用通常不会通过设置symbol-function进行更改。您的代码具有以下问题:1.未定义变量:FN 2.缺少“')此代码不工作(
FN
未绑定)。当然,修复后很高兴恢复到+1。