sun*_*ica 18 lisp common-lisp memoization
我是Lisp的初学者.我正在尝试记忆递归函数来计算Collatz序列中的项数(对于Project Euler中的问题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)))))))
Run Code Online (Sandbox Code Playgroud)
memoize函数与On Lisp书中给出的函数相同.
与非memoized版本相比,此代码实际上没有提供任何加速.我相信这是由于调用函数的非memoized版本的递归调用,这有点违背了目的.在这种情况下,这里进行记忆的正确方法是什么?有没有办法让所有调用原始函数调用memoized版本本身,不需要特殊的m-collatz-steps符号?
编辑:更正了代码
(defvar m-collatz-steps (memoize #'collatz-steps))
Run Code Online (Sandbox Code Playgroud)
这是我在我的代码中所拥有的.在编辑之前我错误地说:
(defvar collatz-steps (memoize #'collatz-steps))
Run Code Online (Sandbox Code Playgroud)
看到这个错误给了我另一个想法,我尝试使用这个最后的defvar本身并更改递归调用
(1+ (funcall collatz-steps (/ n 2)))
(1+ (funcall collatz-steps (1+ (* 3 n))))
Run Code Online (Sandbox Code Playgroud)
这似乎执行了memoization(加速从大约60秒到1.5秒),但需要更改原始功能.有没有更清洁的解决方案,不涉及改变原有的功能?
hua*_*uan 10
我假设您使用的是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))
Run Code Online (Sandbox Code Playgroud)