我正在尝试在Racket中构建内置过程build-list。
内置函数的工作原理如下:
(build-list 10 (lambda (x) (* x x)))
>> '(0 1 4 9 16 25 36 49 64 81)
我的实现是递归过程的递归定义:
(define (my-build-list-recur list-len proc)
(if (= list-len 0)
'()
(cons (proc (sub1 list-len)) (my-build-list-recur (sub1 list-len) proc))))
当我致电我的实施时,我有:
(my-build-list-recur 10 (lambda (x) (* x x)))
>> '(81 64 49 36 25 16 9 4 1 0)
正如您可能已经看到的那样,我得到的结果相同,但顺序相反。
如何使结果与原生函数的顺序相同?
P.S。:我已经使用迭代过程的递归定义完成了一个实现完美的实现。我现在正在努力用完全递归的过程生成相同的结果。我已经知道如何用长尾递归来解决这个疑问。
这是我的长尾递归实现:
(define (my-build-list list-len proc)
(define (iter list-len accu n)
(if (= (length accu) list-len)
(reverse accu)
(iter list-len (cons (proc n) accu) (add1 n))))
;(trace iter)
(iter list-len '() 0))
答案 0 :(得分:2)
好的,所以你正在寻找一个不使用状态变量和尾调用的答案。您想要一个递归的过程,它也会产生一个递归的过程。不确定为什么除了看看定义如何不同之外你想要这个。您还应该阅读尾部递归模数缺点(here和on wikipedia) - 它与此问题相关。
;; recursive procedure, recursive process
(define (build-list n f)
(define (aux m)
(if (equal? m n)
empty
(cons (f m) (aux (add1 m)))))
(aux 0))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
注意aux
调用不再处于尾部位置 - 即,cons
在其参数中评估aux
调用之前无法完成评估。这个过程看起来像这样,在堆栈上演变:
(cons (f 0) ...)
(cons (f 0) (cons (f 1) ...))
(cons (f 0) (cons (f 1) (cons (f 2) ...)))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) ...))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4) ...)))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4) empty)))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4) '())))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) '(16)))))
(cons (f 0) (cons (f 1) (cons (f 2) '(9 16))))
(cons (f 0) (cons (f 1) '(4 9 16)))
(cons (f 0) '(1 4 9 16))
'(0 1 4 9 16)
在cons
填写之前,您会看到...
来电一直处于悬空状态。最后...
未填写{{1}直到empty
等于m
。
如果您不喜欢内部n
过程,则可以使用默认参数,但这会将某些私有API泄露给公共API。也许它对你有用和/或你可能并不在乎。
aux
堆栈安全实施
为什么你特别想要一个递归过程(增长堆栈的过程)很奇怪,尤其是考虑到编写一个不安全的;; recursive procedure, recursive process
(define (build-list n f (m 0))
(if (equal? m n)
'()
(cons (f m) (build-list n f (add1 m)))))
;; still only apply build-list with 2 arguments
(build-list 5 (lambda (x) (* x x)))
;; => '(0 1 4 9 16)
;; if a user wanted, they could start `m` at a different initial value
;; this is what i mean by "leaked" private API
(build-list 5 (lambda (x) (* x x) 3)
;; => '(9 16)
过程是多么容易增长堆栈。这是一些带有线性迭代过程的递归过程。
第一个非常简单,但使用build-list
参数泄漏了一点私有API。您可以使用acc
过程轻松解决此问题,就像我们在第一个解决方案中所做的那样。
aux
查看演进过程
;; recursive procedure, iterative process
(define (build-list n f (acc empty))
(if (equal? 0 n)
acc
(build-list (sub1 n) f (cons (f (sub1 n)) acc))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
这是非常好的,因为它可以不断重用一个堆栈帧,直到构建整个列表。作为一个额外的优势,我们不需要保持从0到(cons (f 4) empty)
(cons (f 3) '(16))
(cons (f 2) '(9 16))
(cons (f 1) '(4 9 16))
(cons (f 0) '(1 4 9 16))
;; => '(0 1 4 9 16)
的计数器。相反,我们会向后构建列表,并从n
计算到n-1
。
最后,这是另一个演化线性迭代过程的递归过程。它使用了一个名为let和continuation的传递方式。循环有助于防止API泄漏。
0
如果您使用;; recursive procedure, iterative process
(define (build-list n f)
(let loop ((m 0) (k identity))
(if (equal? n m)
(k empty)
(loop (add1 m) (λ (rest) (k (cons (f m) rest)))))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
和compose
,它会稍微清理一下:
curry
从这个过程演变而来的过程略有不同,但是你会注意到它也没有增长堆栈,而是在堆上创建一系列嵌套的lambdas。因此,对于足够大的;; recursive procedure, iterative process
(define (build-list n f)
(let loop ((m 0) (k identity))
(if (equal? n m)
(k empty)
(loop (add1 m) (compose k (curry cons (f m)))))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
:
n