我正在使用Racket,但问题适用于任何支持尾递归的Scheme。
我熟悉在平面列表上实现尾递归的传统模式,大致如此:
(define (func x [acc null])
(if (null? x)
acc
(func (cdr x) (cons (do-something-to (car x)) acc))))
在这种情况下,func
处于尾部位置。
但是当我使用树时,即带有递归嵌套列表的列表时,我最终会使用map
进行递归下降,如下所示:
(define (func2 x)
(cond
[(atom? x) (do-something-to x)]
[(list? x) (map func2 x)]))
这样可行,但func2
不再处于尾部位置。
你能 - 如果是的话,你会怎样 - 以尾递归的方式重写func2
?
(暂不考虑它是否会改善表现,这不是我要问的问题。)
答案 0 :(得分:2)
你在技术上“可以”,通过引入一个充当堆栈的累加器。只有在堆栈为空时才会执行您的功能。
但是,它具有与使用函数调用堆栈相同的内存使用要求(即非尾递归),所以通常你不会通过这样做获得任何东西。
答案 1 :(得分:2)
由于已在另一个答案中正确陈述和解释,因此在此方面使用尾递归没有任何优势。但是,正如你对它的实现方式感兴趣一样,这里是我实现过的deep-map
函数。如果您对镜像列表满意,则代码更清晰。
(define deep-map
(λ (f lst)
(let tail-rec ([stack `(,lst)] [acc '(())])
;(displayln (~a "Stack: " stack " / Acc: " acc))
(cond [(null? (car stack))
(if (null? (cdr stack))
(car acc)
(tail-rec (cdr stack)
`(,(append (cadr acc) `(,(car acc))) . ,(cddr acc))))]
;; The first element is a list and is being put on the stack
[(list? (caar stack))
(tail-rec `(,(caar stack) . (,(cdr (car stack)) . ,(cdr stack)))
`(() . ,acc))]
;; Process next element
[else (tail-rec `(,(cdar stack) . ,(cdr stack))
`(,(append (car acc) `(,(f (caar stack)))) . ,(cdr acc)))]
))))
一个简单的例子:
> (deep-map add1 '(1 ((2) 3)))
Stack: ((1 ((2) 3))) / Acc: (())
Stack: ((((2) 3))) / Acc: ((2))
Stack: (((2) 3) ()) / Acc: (() (2))
Stack: ((2) (3) ()) / Acc: (() () (2))
Stack: (() (3) ()) / Acc: ((3) () (2))
Stack: ((3) ()) / Acc: (((3)) (2))
Stack: (() ()) / Acc: (((3) 4) (2))
Stack: (()) / Acc: ((2 ((3) 4)))
'(2 ((3) 4))