方案:按原始顺序重建列表的迭代过程



我的问题是:如何编写一个使用tailcall的过程,并构建一个不按相反顺序的列表。为了说明我的意思,这里有一个非常简单的迭代过程的例子,它创建了一个列表的副本:

(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放在一起的迭代顺序,返回的列表最终会反转。是的,您可以通过在if-块中执行(reverse cp-list)而不是仅执行cp-list来轻松解决此问题,但问题是reverse是一个递归过程。使用此过程将抵消尾调用的优势,即堆栈大小随输入大小线性增长。

那么,基本上,如何编写像上面提到的那样的过程,以正确的顺序返回列表,而不使用任何递归过程

感谢

请注意,您的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

正如评论中提到的,并不是所有这些方法都使用迭代过程。

最新更新