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…
不同于justreturn
,后者计算以符号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…
不同于justreturn
,后者计算以符号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)))))