尾递归版本列表附加函数

时间:2012-11-06 16:55:50

标签: lisp scheme racket tail-call-optimization tailrecursion-modulo-cons

我看到了几个将append元素实现到列表中的示例,但所有元素都没有使用尾递归。如何在功能风格中实现这样的功能?

 (define (append-list lst elem) expr)

5 个答案:

答案 0 :(得分:6)

以下是tail recursion modulo cons优化的实现,从而产生完全尾递归代码。它复制输入结构,然后通过变异以自上而下的方式将新元素附加到它。由于这个突变是对其内部新创建的数据进行的,因此它仍然在外部起作用(不会改变传递给它的任何数据,并且除了产生结果之外没有任何可观察到的影响):

(define (add-elt lst elt)
  (let ((result (list 1)))
    (let loop ((p result) (lst lst))
      (cond 
        ((null? lst) 
           (set-cdr! p (list elt)) 
           (cdr result))
        (else 
           (set-cdr! p (list (car lst)))
           (loop (cdr p) (cdr lst)))))))

我喜欢使用“head-sentinel”技巧,它以分配一个额外的cons单元为代价大大简化了代码。

此代码使用低级突变原语来完成某些语言(例如Prolog)由编译器自动完成的操作。在TRMC优化假设方案中,我们将能够编写以下尾递归模数缺点代码,并让编译器自动将其转换为上述代码的某些代码:

(define (append-elt lst elt)              ;; %% in Prolog:
  (if (null lst)                          ;; app([], X, Z) :- Z=[X].
    (list elt)                            ;; app([A|B], X, Z) :-
    (cons (car lst)                       ;;   Z=[A|Y],         % cons _before_
          (append-elt (cdr lst) elt))))   ;;   app( B, X, Y).   % tail call

如果不是cons操作,append-elt 尾递归。这就是TRMC优化发挥作用的地方。

答案 1 :(得分:4)

可以写一个尾递归append-element程序......

(define (append-element lst ele)
  (let loop ((lst (reverse lst))
             (acc (list ele)))
    (if (null? lst)
        acc
        (loop (cdr lst) (cons (car lst) acc)))))

...但是reverse投入的效率更低(为了更好的衡量标准)。我想不出另一个功能(例如,没有修改输入列表)的方式将此过程编写为尾递归而不首先反转列表。

对于问题的非功能性答案,@ WillNess提供了一个很好的Scheme解决方案,可以改变内部列表。

答案 2 :(得分:2)

你不能天真,但也要看到提供TCMC的实现 - Tail Call Modulo Cons。这允许

(cons head TAIL-EXPR)

如果利弊本身是尾部调用,则尾部调用TAIL-EXPR

答案 3 :(得分:2)

这是一个使用continuation的函数尾递归append-elt:

(define (cont-append-elt lst elt)
  (let cont-loop ((lst lst)
                  (cont values))
    (if (null? lst)
        (cont (cons elt '()))
        (cont-loop (cdr lst)
                   (lambda (x) (cont (cons (car lst) x)))))))

性能方面,它接近Will在Racket和Gambit中的变异,但在Ikarus和Chicken中,Óscar的反向做得更好。尽管如此,突变总是表现最好的。我不会用这个,但是Óscar的一个轻微版本的条目,纯粹是因为它更容易阅读。

(define (reverse-append-elt lst elt)
  (reverse (cons elt (reverse lst))))

如果你想改变性能我会做的:

(define (reverse!-append-elt lst elt)
  (let ((lst (cons elt (reverse lst))))
     (reverse! lst)
     lst))

答案 4 :(得分:1)

这是Lisp,不是Scheme,但我相信你可以翻译:

(defun append-tail-recursive (list tail)
  (labels ((atr (rest ret last)
             (if rest
                 (atr (cdr rest) ret
                      (setf (cdr last) (list (car rest))))
                 (progn
                   (setf (cdr last) tail)
                   ret))))
    (if list
        (let ((new (list (car list))))
          (atr (cdr list) new new))
        tail)))

我将头部保留在返回列表的尾部,并在遍历列表参数时修改尾部。