Я новичок Lisp. Я пытаюсь memoize рекурсивную функцию для вычисления количества терминов в последовательности Collatz (для проблемы 14 в Project Euler). Мой код на данный момент:
(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 такая же, как и в Вкл. Lisp.
Этот код фактически не дает ускорения по сравнению с не memoized версией. Я считаю, что это связано с рекурсивными вызовами, вызывающими не-мемуаризованную версию функции, которая вроде бы поражает цель. В этом случае, каков правильный способ сделать меморандум здесь? Есть ли способ, чтобы все вызовы исходной функции вызывали сама memoized, устраняя необходимость в специальном символе шага m-collatz?
EDIT: Исправлен код для
(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))))
Кажется, что он выполняет memoization (ускорение от 60 секунд до 1,5 секунд), но требует изменения оригинальной функции. Есть ли более чистое решение, которое не связано с изменением исходной функции?