Я новичок в Лиспе. Я пытаюсь запомнить рекурсивную функцию для вычисления количества членов в последовательности Коллатца (для задачи 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)))))))
Функция памятки та же, что и в книге на Лиспе .
Этот код фактически не дает никакого ускорения по сравнению с незаписанной версией. Я полагаю, что это происходит из-за рекурсивных вызовов, вызывающих незарегистрированную версию функции, что в некоторой степени противоречит цели. В таком случае, как правильно сделать памятку здесь? Есть ли способ, чтобы все вызовы исходной функции вызывали саму запомненную версию, устраняя необходимость в специальном символе m-collatz-steps?
РЕДАКТИРОВАТЬ: исправил код, чтобы иметь
(defvar m-collatz-steps (memoize #'collatz-steps))
что и было в моем коде.
Перед правкой я ошибочно поставил:
(defvar collatz-steps (memoize #'collatz-steps))
Увидев эту ошибку, у меня появилась другая идея, и я попытался использовать этот последний дефвар и изменить рекурсивные вызовы на
(1+ (funcall collatz-steps (/ n 2)))
(1+ (funcall collatz-steps (1+ (* 3 n))))
Похоже, что это выполняет запоминание (ускорение от 60 секунд до 1,5 секунд), но требует изменения исходной функции. Есть ли более чистое решение, которое не предполагает изменения первоначальной функции?