Recursion 公共Lisp中的递归、推送值和斐波那契序列

Recursion 公共Lisp中的递归、推送值和斐波那契序列,recursion,lisp,common-lisp,fibonacci,clisp,Recursion,Lisp,Common Lisp,Fibonacci,Clisp,这不是家庭作业。在以下代码中: (defparameter nums '()) (defun fib (number) (if (< number 2) number (push (+ (fib (- number 1)) (fib (- number 2))) nums)) return nums) (format t "~a " (fib 100)) (定义参数nums'()) (数字) (如果(

这不是家庭作业。在以下代码中:

(defparameter nums '())

(defun fib (number)
  (if (< number 2)
      number
    (push (+ (fib (- number 1)) (fib (- number 2))) nums))
  return nums)

(format t "~a " (fib 100))
(定义参数nums'())
(数字)
(如果(<数字2)
数字
(推送(+(fib(-1号))(fib(-2号)))nums))
返回nums)
(格式t“~a”(fib 100))
由于我对Common Lisp缺乏经验,我不明白为什么函数不返回值。我正在尝试打印Fibonacci序列的第一个“n”值,例如100


谢谢。

您的函数无条件返回
nums
(但仅当名为
return
的变量存在时)。要了解原因,我们可以将其格式化如下:

(defun fib (number)
  (if (< number 2)
    number
    (push (+ (fib (- number 1)) (fib (- number 2))) nums))
  return 
  nums)
(defun f (...)
  ...
  ;; no function bindings or notinline declarations of F here
  ...
  (f ...)
  ...)
某些标准控制流和循环构造建立隐藏的匿名块,因此可以使用
return

(defun function ()
  (return-from function 42) ;; function terminates, returns 42
  (print 'notreached))     ;; this never executes
 (dolist (x '(1 2 3))
   (return 42))            ;; loop terminates, yields 42 as its result
如果我们使用
(return…
,但是没有封闭的匿名块,那就是一个错误

表达式
(return…
不同于just
return
,后者计算以符号
return
命名的变量,检索其内容


不清楚如何修复您的
fib
功能,因为需求未知。将值推入全局列表的副作用通常不属于这样的数学函数内部,它应该是纯函数(无副作用)。

您的函数无条件返回
nums
(但仅当存在名为
return
的变量时)。要了解原因,我们可以将其格式化如下:

(defun fib (number)
  (if (< number 2)
    number
    (push (+ (fib (- number 1)) (fib (- number 2))) nums))
  return 
  nums)
(defun f (...)
  ...
  ;; no function bindings or notinline declarations of F here
  ...
  (f ...)
  ...)
某些标准控制流和循环构造建立隐藏的匿名块,因此可以使用
return

(defun function ()
  (return-from function 42) ;; function terminates, returns 42
  (print 'notreached))     ;; this never executes
 (dolist (x '(1 2 3))
   (return 42))            ;; loop terminates, yields 42 as its result
如果我们使用
(return…
,但是没有封闭的匿名块,那就是一个错误

表达式
(return…
不同于just
return
,后者计算以符号
return
命名的变量,检索其内容


不清楚如何修复您的
fib
功能,因为需求未知。将值推入全局列表的副作用通常不属于这样的数学函数,它应该是纯的(无副作用)。

计算斐波那契数的一个明显方法是:

(defun fib (n)
  (if (< n 2)
      n
    (+ (fib (- n 1)) (fib (- n 2)))))

(defun fibs (n)
  (loop for i from 1 below n
        collect (fib i)))
这将保留一个表——一个列表——它迄今为止计算的结果,并使用它来避免重新计算

使用此功能的记忆版本:

> (time (fib 1000))
Timing the evaluation of (fib 1000)

User time    =        0.000
System time  =        0.000
Elapsed time =        0.000
Allocation   = 101944 bytes
0 Page faults
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
上述定义对
fib
的每次调用都使用一个新的缓存:这很好,因为本地函数
fibber
确实重用了缓存。但如果将缓存放在函数之外,则可以做得更好:

(defmacro define-function (name expression)
  ;; Install EXPRESSION as the function value of NAME, returning NAME
  ;; This is just to avoid having to say `(setf ...)`: it should
  ;; probably do something at compile-time too so the compiler knows
  ;; the function will be defined.
  `(progn
     (setf (fdefinition ',name) ,expression)
     ',name))

(define-function fib
  (let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
    (lambda (n)
      (block fib
        (check-type n (integer 0) "natural number")
        (labels ((fibber (m)
                   (when (> m (car (first so-far)))
                     (push (cons m (+ (fibber (- m 1))
                                      (fibber (- m 2))))
                           so-far))
                   (cdr (assoc m so-far))))
          (fibber n))))))
此版本的
fib
将在调用之间共享其缓存,这意味着它速度更快,分配的内存更少,但线程安全性可能更低:

> (time (fib 1000))
[...]
Allocation   = 96072 bytes
[...]

> (time (fib 1000))
[...]
Allocation   = 0 bytes
[...]
有趣的是,记忆是由唐纳德·米奇发明的(或者至少是命名的),他致力于打破Tunny(因此与巨像公司合作),我对他也略知一二:计算的历史仍然很短


请注意,备忘录化是您可能最终与编译器展开战斗的时刻之一。特别是对于这样的函数:

(defun fib (number)
  (if (< number 2)
    number
    (push (+ (fib (- number 1)) (fib (- number 2))) nums))
  return 
  nums)
(defun f (...)
  ...
  ;; no function bindings or notinline declarations of F here
  ...
  (f ...)
  ...)
然后,编译器可以(但不是必需的)假设对
f
的明显递归调用是对它正在编译的函数的递归调用,从而避免了整个函数调用的大量开销。特别是,不需要检索符号
f
的当前函数值:它可以直接调用函数本身

这意味着试图编写一个函数,
memoize
,该函数可用于对现有递归函数进行mamoize,如
(setf(fdefinition'f)(memoize#'f))
可能不起作用:函数
f
仍然直接调用自身的未加密版本,并且不会注意到
f
的函数值已更改

事实上,即使在许多情况下递归是间接的,这也是正确的:允许编译器假定对同一文件中有定义的函数
g
的调用是对该文件中定义的版本的调用,并再次避免完全调用的开销

处理此问题的方法是添加适当的
notinline
声明:如果调用包含在
notinline
声明中(编译器必须知道该声明),则必须将其作为完整调用进行。从:

编译器不能随意忽略此声明;对指定函数的调用必须作为越位子例程调用实现

这意味着,为了记忆函数,必须为递归调用添加合适的
notinline
声明,这意味着记忆要么需要通过宏完成,要么必须依赖于用户向要记忆的函数添加合适的声明


这只是一个问题,因为CL编译器被允许是智能的:几乎总是一件好事

计算斐波那契数的一个明显方法是:

(defun fib (n)
  (if (< n 2)
      n
    (+ (fib (- n 1)) (fib (- n 2)))))

(defun fibs (n)
  (loop for i from 1 below n
        collect (fib i)))
这将保留一个表——一个列表——它迄今为止计算的结果,并使用它来避免重新计算

使用此功能的记忆版本:

> (time (fib 1000))
Timing the evaluation of (fib 1000)

User time    =        0.000
System time  =        0.000
Elapsed time =        0.000
Allocation   = 101944 bytes
0 Page faults
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
上述定义对
fib
的每次调用都使用一个新的缓存:这很好,因为本地函数
fibber
确实重用了缓存。但如果将缓存放在函数之外,则可以做得更好:

(defmacro define-function (name expression)
  ;; Install EXPRESSION as the function value of NAME, returning NAME
  ;; This is just to avoid having to say `(setf ...)`: it should
  ;; probably do something at compile-time too so the compiler knows
  ;; the function will be defined.
  `(progn
     (setf (fdefinition ',name) ,expression)
     ',name))

(define-function fib
  (let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
    (lambda (n)
      (block fib
        (check-type n (integer 0) "natural number")
        (labels ((fibber (m)
                   (when (> m (car (first so-far)))
                     (push (cons m (+ (fibber (- m 1))
                                      (fibber (- m 2))))
                           so-far))
                   (cdr (assoc m so-far))))
          (fibber n))))))
此版本的
fib
将在调用之间共享其缓存,这意味着它速度更快,分配的内存更少,但线程安全性可能更低:

> (time (fib 1000))
[...]
Allocation   = 96072 bytes
[...]

> (time (fib 1000))
[...]
Allocation   = 0 bytes
[...]
有趣的是,记忆是由唐纳德·米奇发明的(或者至少是命名的),他致力于打破Tunny(因此与巨像公司合作),我对他也略知一二:计算的历史是s
(if (< a b)
    5
    10)
(+ 10 
   (if (< a b)
       5
       10))
(defun fib (n)
  (if (zerop n)
      n
      (+ (fib (1- n)) (fib (- n 2)))))