例如,在Scheme中使用以下简单的右侧折叠实现:
(define (fold-rite kons knil clist)
(if (null? clist)
knil
(kons (car clist) (fold-rite kons knil (cdr clist)))))
这显然不是尾部调用消除的候选者,因为fold-rite
的递归调用必须在调用kons
之前完成。现在,我可能会有点聪明,而是使用延续传递方式:
(define (fold-rite-cps kons knil clist kontinuation)
(if (null? clist)
(kontinuation knil)
(fold-rite-cps kons knil (cdr clist)
(lambda (x) (kontinuation (kons (car clist) x))))))
现在fold-rite-cps
是尾递归的,但在递归期间建立的中间连续仍然必须保留在某处。因此,虽然我可能没有吹出堆栈,但我仍然使用与第一个版本一样多的内存,并且不知何故我感觉首先收集了大量的延续,然后一举将它们全部解开应该是性能不好,虽然第二个版本在我的机器上实际上要快得多。
但是如果左侧折叠可以在恒定空间中运行(减去累积的值)并且列表上的右侧折叠与其反向上的左侧折叠相同,那么我想应该有一种方法来实现尾部 - 也可以在恒定空间中运行的递归右折叠。如果是这样,我该怎么做?更一般地说,有什么方法可以将非尾递归函数转换成尾递归函数,最好是可以在常量空间中运行的函数(假设显式循环可以用也在常量运行的命令式语言编写)空间)?我做了什么错误的假设吗?
虽然我已经标记了Scheme和Lisp,但我对任何语言的解决方案感兴趣;这些方法应该适用于一般的功能程序。
答案 0 :(得分:1)
基于上面的评论,我认为在不改变数据结构(缺点单元格)的情况下,最好的答案将如下(在常见的lisp中,因为这是我的方便)。
由于cons单元的单个链接结构,为了不积累空间,我们将不得不几乎切换列表的顺序,然后折叠反转列表。反转是线性空间操作,根据所使用的减少函数,减少可以为常数空间。
(defun foldl (op clist &optional base)
(if (null clist)
base
(foldl op (cdr clist)
(funcall op (car clist) base))))
(defun foldr (op clist &optional base)
;; reverse the list then fold it
(foldl op (foldl #'cons clist nil) base))
直接回答:不,不可能在恒定空间中折叠,因为cons单元的单个链接性质需要完整列表遍历才能到达最后一个元素,然后是展开步骤或单独的列表来跟踪实际折叠操作。