给定递归函数,如何将其更改为尾递归和流?

问题描述 投票:3回答:3

给出方案中的递归函数,如何将其更改为尾递归,然后如何使用流实现它?以这种方式更改任何功能时,您是否遵循模式和规则?

以此函数为例,该函数创建2-m的数字列表(这不是尾递归吗?]

代码:

(define listupto
  (lambda (m)
    (if (= m 2)
        '(2)
        (append (listupto (- m 1)) (list m)))))
recursion stream scheme tail-recursion
3个回答
4
投票

我将首先解释您的示例。绝对不是尾递归。考虑一下此函数的执行方式。每次追加时,您都必须先返回并进行递归调用,直到遇到基本情况,然后再往回拉。

这就是您的函数痕迹:

(listupto 4)
| (append (listupto(3)) '4)
|| (append (append (listupto(2)) '(3)) '(4))
||| (append (append '(2) '(3)) '(4))
|| (append '(2 3) '(4))
| '(2 3 4)
'(2 3 4)

请注意,您看到的V模式会先拉入然后再退出递归调用。尾部递归的目标是将所有调用组合在一起,并且只执行一个。您需要做的是将一个累加器与函数一起传递,这样,当函数到达基本情况时,您只能添加一个追加。

这里是函数的尾部递归版本:

(define listupto-tail
  (lambda (m)
     (listupto m '())))

# Now with the new accumulator parameter!
(define listupto
   (lambda (m accu)
     (if (= m 2)
        (append '(2) accu)
        (listupto (- m 1) (append (list m) accu)))))

如果我们看到此跟踪,它将看起来像这样:

(listupto 4)
| (listupto (3) '(4))  # m appended with the accu, which is the empty list currently
|| (listupto (2) '(3 4)) # m appended with accu, which is now a list with 4
||| (append '(2) '(3 4))
'(2 3 4)

请注意模式有何不同,我们不必遍历递归调用。这样可以节省我们毫无意义的处决。尾递归可能是一个很难理解的概念,我建议您看一下here。第5章包含一些有用的部分。


3
投票

通常切换到尾部递归形式,您需要转换代码,以便它采用一个累加器参数,该参数会累积结果并用作最终返回值。通常这是您的主函数也委托的一个辅助函数。

形式的东西:

(define listupto
  (lambda (m)
     (listupto-helper m '())))

(define listupto-helper
   (lambda (m l)
     (if (= m 2)
        (append '(2) l)
         (listupto-helper (- m 1) (append (list m) l)))))

正如评论所指出,可以用一个命名的let来代替helper函数,该名字显然(没有做太多/足够的Scheme!)(而且正如评论所建议的,cons比创建列表要好得多)并附加。

(define listupto
  (lambda (n)
    (let loop ((m n) (l '()))
      (if (= m 2)
          (append '(2) l)
          (loop (- m 1) (cons m l))))))

0
投票

您还询问流。您可以找到使用的SICP样式的流,例如herehere,其中定义了from-By流构建器:

 ;;;; Stream Implementation
 (define (head s) (car s))
 (define (tail s) ((cdr s))) 

 (define-syntax s-cons
   (syntax-rules () ((s-cons h t) (cons h (lambda () t))))) 

 ;;;; Stream Utility Functions
 (define (from-By x s)
   (s-cons x (from-By (+ x s) s)))

这种流创建依赖于宏,必须通过特殊方式对其进行访问:

 (define (take n s) 
   (cond
     ((> n 0) (cons (head s) (take (- n 1) (tail s))))
      ;; ^^ **WRONG!** it should be
      ;; ((> n 1) (cons (head s) (take (- n 1) (tail s))))
      ;; ((= n 1) (list (head s)))
     (else '())))

 (define (drop n s)
   (cond ((> n 0) (drop (- n 1) (tail s)))
     (else s)))

但是它们不是持久性的,即takedrop在每次访问时都会重新计算它们。要生成持久流,您需要安排尾部封闭以外科方式更改访问中的最后一对:

(1 . <closure-1>)
(1 . (2 . <closure-2>))
....

像这样:

(define (make-stream next this init)
  (let ((cc (list (this init))))  ; last cons cell
    (letrec ((g (lambda ()
                    (set! init (next init))
                    (set-cdr! cc (cons (this init) g))
                    (set! cc (cdr cc))
                    cc)))
      (set-cdr! cc g)
      cc)))

(define (head s) (car s))

(define (tail s)
  (if (pair? (cdr s)) (cdr s)
    (if (not (null? (cdr s)))
      ((cdr s))
      '())))

我们现在可以像这样使用它

(define a (make-stream (lambda (i) (+ i 1)) (lambda (i) i) 1))
;Value: a

a
;Value 13: (1 . #[compound-procedure 14])

(take 3 a)
;Value 15: (1 2 3)

a
;Value 13: (1 2 3 4 . #[compound-procedure 14])

(define b (drop 4 a))
;Value: b

b
;Value 16: (5 . #[compound-procedure 14])

a
;Value 13: (1 2 3 4 5 . #[compound-procedure 14])

(take 4 a)
;Value 17: (1 2 3 4)

a
;Value 13: (1 2 3 4 5 . #[compound-procedure 14])

现在,(make-stream (lambda (i) (list (cadr i) (+ (car i) (cadr i)))) car (list 0 1))定义什么?

© www.soinside.com 2019 - 2024. All rights reserved.