问题描述
我的问题是:如何编写一个使用尾调用的过程,该过程不能以相反的顺序构造一个列表。 为了说明我的意思,下面是一个非常简单的过程的示例,该过程是迭代的,它会创建列表的副本:
(define (copy-list ls)
(define (iter cp-ls rest-ls)
(if (null? rest-ls)
cp-ls
(iter (cons (car rest-ls) cp-ls) (cdr rest-ls))))
(iter '() ls))
问题是,由于元素cons
在一起的迭代顺序,返回的列表最终相反。是的,您可以通过执行(reverse cp-list)
而不是在cp-list
块中仅执行if
来解决此问题,但是问题在于reverse
是一个递归过程。利用此过程将使tailcall的优势无效,因为堆栈大小随输入大小线性增长。
因此,基本上,如何编写像上述程序那样使用任何递归过程以正确顺序不返回列表的程序?
谢谢
解决方法
请注意,您的iter
过程几乎完全是 首先实现reverse
的方式-不,这不是您在问题中提到的递归过程。
在Racket中,很容易检查过程的定义:在没有定义的编辑窗口中,键入reverse
,右键单击它,然后选择“跳转到定义”。它会有一些额外的代码用于优化和错误处理,但是算法的核心在letrec-values
部分,它与您的iter
过程相同。所以,如果我们已经拥有了:
(define (reverse ls)
(let iter ((cp-ls '()) (rest-ls ls))
(if (null? rest-ls)
cp-ls
(iter (cons (car rest-ls) cp-ls) (cdr rest-ls)))))
然后,copy-list
就是:
(define (copy-list ls)
(reverse (reverse ls)))
顺便说一句,这并不是非常有用-如果您不对列表进行变异,则复制它毫无意义。而反向的反向只是原始的东西,不是吗?实际上,出于所有意图和目的,在不可变列表上运行的copy-list
的任何实现都等同于标识过程:
(define (copy-list ls) ls)
,
您可以在下面使用连续传递样式return
-
(define (copy-list ls)
(let loop ((ls ls) (return identity))
(if (null? ls)
(return null)
(loop (cdr ls)
(lambda (r) (return (cons (car ls) r)))))))
(copy-list '(1 2 3 4))
; '(1 2 3 4)
这是过程的样子-
(copy-list '(1 2 3 4))
(loop '(1 2 3 4) identity)
(loop '(2 3 4) (lambda (r) (identity (cons 1 r))))
(loop '(3 4) (lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))))
(loop '(4) (lambda (r) ((lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))) (cons 3 r))))
(loop '() (lambda (r) ((lambda (r) ((lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))) (cons 3 r))) (cons 4 r))))
((lambda (r) ((lambda (r) ((lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))) (cons 3 r))) (cons 4 r))) null)
((lambda (r) ((lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))) (cons 3 r))) '(4))
((lambda (r) ((lambda (r) (identity (cons 1 r))) (cons 2 r))) '(3 4))
((lambda (r) (identity (cons 1 r))) '(2 3 4))
(identity '(1 2 3 4))
'(1 2 3 4)
,
(map (lambda(x) x) l)
将复制列表l
,并且不会递归编写。
(let ((l '(1 2 3 4)))
((fold-right (lambda (e acc)
(lambda (x) (cons x (acc e))))
(lambda (x) (list x))
(cdr l))
(car l)))
是另一种无需递归但使用monoid复制列表的形式。
其他:
(let ((l '(1 2 3 4)))
(car ((fold-right (lambda (e acc)
(lambda (x) (acc (append x (list e)))))
(lambda (x) (list x))
(cdr l))
(list (car l)))))
其他:
(let ((l '(1 2 3 4)))
(cdr ((fold-left (lambda (acc e)
(lambda (x) (cons x (acc e))))
(lambda (x) (list x))
l)
'first)))
其他(由Will建议):
(let ((l '(1 2 3 4)))
((fold-right (lambda (e acc)
(lambda (k) (k (acc (lambda (es) (cons e es))))))
(lambda (z) (z (list))) l)
(lambda (es) es)))
还有许多其他方法可以复制列表。通常,要制作副本,您需要直接或间接致电cons
。
如评论中所述,并非所有这些方式都使用迭代过程。