Given a recursive function, how do I change it to tail recursive and streams?

天大地大妈咪最大 提交于 2020-04-11 04:20:11

问题


Given a recursive function in scheme how do I change that function to tail recursive, and then how would I implement it using streams? Are there patterns and rules that you follow when changing any function in this way?

Take this function as an example which creates a list of numbers from 2-m (this is not tail recursive?)

Code:

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

回答1:


I'll start off by explaining your example. It is definitely not tail recursive. Think of how this function executes. Each time you append you must first go back and make the recursive call until you hit the base case, and then you pull your way back up.

This is what a trace of you function would look like:

(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)

Notice the V-pattern you see pulling in and then out of the recursive calls. The goal of tail recursion is to build all of the calls together, and only make one execution. What you need to do is pass an accumulator along with your function, this way you can only make one append when your function reaches the base case.

Here is the tail recursive version of your function:

(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)))))

If we see this trace, it will look like this:

(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)

Notice how the pattern is different, and we don't have to traverse back through the recursive calls. This saves us pointless executions. Tail recursion can be a difficult concept to grasp I suggest taking a look here. Chapter 5 has some helpful sections in it.




回答2:


Generally to switch to a tail recursive form you transform the code so that it takes an accumulator parameter which builds the result up and is used as the final return value. This is generally a helper function which your main function delegates too.

Something of the form:

(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)))))

As the comments point out, the helper function can be replaced with a named let which is apparently (haven't done much/enough Scheme!) more idiomatic (and as the comments suggest cons is much better than creating a list and appending.

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



回答3:


You also ask about streams. You can find a SICP styled streams used e.g. here or here which have a from-By stream builder defined:

 ;;;; 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)))

Such streams creation relies on macros, and they must be accessed by special means:

 (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)))

But they aren't persistent, i.e. take and drop recalculate them on each access. To make persistent streams you'd need to arrange for the tailing closure to surgically alter the last pair on access:

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

like this:

(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))
      '())))

We can now use it like this

(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])

Now, what does (make-stream (lambda (i) (list (cadr i) (+ (car i) (cadr i)))) car (list 0 1)) define?



来源:https://stackoverflow.com/questions/11654004/given-a-recursive-function-how-do-i-change-it-to-tail-recursive-and-streams

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!