(rev '(aye bee sea)) ; (sea bee aye) (rev '(ex)) ; (ex) (rev '()) ; ()
(define rev (lambda (lis) (accumulate (lambda (x y) (append y x)) (map list lis))))but let's say we want to do it recursively.
(define rev (lambda (lis) (if (null? lis) '() (append (rev (cdr lis)) (list (car lis))))))Let's look at a call graph for (rev '(a b c d)), to make sure we understand how it works.
That's nice, but maybe inefficient, because it is expensive to tack things onto the end of a list but cheap to tack them onto the front.
(rev-aux '(aye bee sea) '(one two three)) ; (three two one aye bee sea)So let's define it:
(define rev (lambda (lis) (rev-aux '() lis))) (define rev-aux (lambda (end-of-result to-be-reversed) (if (null? to-be-reversed) end-of-result (rev-aux (cons (car to-be-reversed) end-of-result) (cdr to-be-reversed)))))This should be efficient because it only calls quick little functions like car and cons, rather than append, which takes time proportional to the length of its first argument.
Let's look at a call graph of (rev '(a b c d)), to see if we
can understand this.
For example:
(pp + '(1 1 1 1 1)) ; (1 2 3 4 5) (pp + '(1000 200 30 4)) ; (1000 1200 1230 1234) (pp + '(14)) ; (14) (pp + '()) ; ()As with most problems, there are a bunch of different strategies we might use here.
(define pp (lambda (f lis) (if (null? lis) '() (let ((fir (car lis))) (cons fir (map (lambda (x) (f x fir)) (pp f (cdr lis))))))))This looks nice, except one might worry about efficiency, in that shared partial sums are not being used. The computation of the next-to-last element is completely separate from the computation of the last element.
(define pp (lambda (f lis) (define butlast (lambda (lis) (reverse (cdr (reverse lis))))) (define last (lambda (lis) (car (reverse lis)))) (cond ((null? lis) '()) ((null? (cdr lis)) lis) (else (let ((partial-pre (pp f (butlast lis)))) (append partial-pre (list (f (last lis) (last partial-pre)))))))))This seems elegant, in that it uses the already-computed total for the first part of the list in calculating the remaining part, so it will not more calls to f than necessary. But it will be inefficient for a different reason: car is fast, while butlast takes time of order the length of the list, as does the call to append.
(define pp (lambda (f lis) (if (null? lis) '() (cons (car lis) (pp-aux f (car lis) (cdr lis)))))) (define pp-aux (lambda (f running-total lis) (if (null? lis) '() (let ((new-running-total (f running-total (car lis)))) (cons new-running-total (pp-aux f new-running-total (cdr lis)))))))Which is efficient both in its manipulation of the list and in taking advantage of the left-to-right structure of the accumulation of the results.
Let's look at it's call graph for (pp + '(0 1000 200 30 4))
Each element in the sequence is the sum of the previous two elements: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, etc.
We want to define our function such that
(fibb 0) ; 0 (fibb 1) ; 1 (fibb 2) ; 1 (fibb 3) ; 2 (fibb 4) ; 3 (fibb 5) ; 5 (fibb 6) ; 8 (fibb 7) ; 13 (fibb 8) ; 21 (fibb 9) ; 34The obvious recursive definition is:
(define fibb (lambda (n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fibb (- n 1)) (fibb (- n 2)))))))This will be very inefficient for large arguments, because Scheme will calculate the same Fibonacci numbers over and over many times. In fact the total number of recursive calls is going up exponentially with the argument! We can see that by sketching the call graph of (fibb 9)
Instead we would like to sum things up as we would if we were doing this by hand. Doing it by hand, we would keep track of the previous two Fibonacci numbers to calculate the next one, and repeat that until we get up to where we want to be.
This can be done with a helper function that takes three arguments: the cdr two Fibonacci numbers, and the number of steps that remain to be taken.
(define fibb (lambda (n) (fibb-aux 0 1 (- n 1)))) (define fibb-aux (lambda (fibb-nm1 fibb-n steps-remaining) (cond ((= steps-remaining 0) fibb-n) ((= steps-remaining -1) fibb-nm1) (else (fibb-aux fibb-n (+ fibb-nm1 fibb-n) (- steps-remaining 1))))))This gives a much faster algorithm, as seen from the following call graph for (fibb 9)