这是解决此问题的一种方法,它通过定义无限的 Pell 数流来工作。这是基于SICP,尤其是section 3.5 中提出的想法。每个人都应该读这本书。
首先,我们需要定义一个结构,让我们讨论无限数据结构。我们通过延迟评估除了其中有限部分之外的所有内容来做到这一点。因此,从一个名为 delay 的宏开始,它会延迟对表单的评估,返回一个“承诺”(当然这是一个函数),以及一个名为 force 的函数,它会强制系统兑现其承诺:
(defmacro delay (form)
;; Delay FORM, which may evaluate to multiple values. This has
;; state so the delayed thing is only called once.
(let ((evaluatedp-n (make-symbol "EVALUATEDP"))
(values-n (make-symbol "VALUES")))
`(let ((,evaluatedp-n nil) ,values-n)
(lambda ()
(unless ,evaluatedp-n
(setf ,evaluatedp-n t
,values-n (multiple-value-list
(funcall (lambda () ,form)))))
(values-list ,values-n)))))
(defun force (promise)
;; force a promise (delayed thing)
(funcall promise))
(对于我们的目的来说,这个实现有点过于复杂,但这是我必须要做的。)。
现在我们将使用delay 来定义流,它们可能是无限的conses 链。这些上的操作对应于 conses 上的操作,但前缀为stream-,还有一个名为null-stream 的对象对应于()(实际上在这个实现中是同一个对象)。
(defmacro stream-cons (car cdr)
;; a cons whose cdr is delayed
`(cons ,car (delay ,cdr)))
(defun stream-car (scons)
;; car of a delayed cons
(car scons))
(defun stream-cdr (scons)
;; cdr of a delayed cons, forced
(force (cdr scons)))
(defconstant null-stream
;; the empty delayed cons
nil)
(defun stream-null (stream)
;; is a delayed cons empty
(eq stream null-stream))
现在定义一个函数pell-stream,它返回一个佩尔数流。此函数手工制作流的前两个元素,然后使用生成器制作其余元素。
(defun pell-stream ()
;; A stream of Pell numbers
(labels ((pell (pn pn-1)
(let ((p (+ (* 2 pn) pn-1)))
(stream-cons p (pell p pn)))))
(stream-cons 0 (stream-cons 1 (pell 1 0)))))
现在我们可以简单地重复使用stream-cdr 来计算佩尔数。
(defun n-pell-numbers (n)
(loop repeat n
for scons = (pell-stream) then (stream-cdr scons)
collect (stream-car scons)))
现在
> (n-pell-numbers 20)
(0
1
2
5
12
29
70
169
408
985
2378
5741
13860
33461
80782
195025
470832
1136689
2744210
6625109)
请注意,事实上,pell-stream 可以是全局变量:它不需要是函数:
(defparameter *pell-stream*
(labels ((pell (pn pn-1)
(let ((p (+ (* 2 pn) pn-1)))
(stream-cons p (pell p pn)))))
(stream-cons 0 (stream-cons 1 (pell 1 0)))))
(defun n-stream-elements (stream n)
(loop repeat n
for scons = stream then (stream-cdr scons)
collect (stream-car scons)))
如果我们定义一个小基准测试程序:
(defun bench-pell (n)
(progn (n-stream-elements *pell-stream* n) n))
然后有趣的是,这显然本质上是一个线性过程(对于后面的元素它会变慢,因为数字变大,因此对它们的操作需要很长时间),并且承诺的有状态实现使其第一次迭代后快得多(以保留相当多的 bignums 为代价):
> (time (bench-pell 100000))
Timing the evaluation of (bench-pell 100000)
User time = 2.020
System time = 0.803
Elapsed time = 2.822
Allocation = 1623803280 bytes
441714 Page faults
100000
> (time (bench-pell 100000))
Timing the evaluation of (bench-pell 100000)
User time = 0.007
System time = 0.000
Elapsed time = 0.006
Allocation = 1708248 bytes
0 Page faults
100000