(rev '(aye bee sea)) ; (sea bee aye) (rev '(ex)) ; (ex) (rev '()) ; ()
(define (rev sen) (accumulate (lambda (x y) (sentence y x)) sen))but let's say we want to do it recursively.
(define (rev sen) (if (empty? sen) '() (sentence (rev (butfirst sen)) (first sen))))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 sentence but cheap to tack them onto the beginning.
(rev-aux '(aye bee sea) '(one two three)) ; (three two one aye bee sea)So let's define it:
(define (rev sen) (rev-aux '() sen)) (define (rev-aux end-of-result to-be-reversed) (if (empty? to-be-reversed) end-of-result (rev-aux (sentence (first to-be-reversed) end-of-result) (butfirst to-be-reversed))))This should be efficient because it only moneys around with the beginning of sentences.
Let's look at a call graph of (rev '(a b c d)), to see if we
can understand this.
(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+ sen) (if (empty? sen) '() (let ((fir (first sen))) (sentence fir (every (lambda (x) (+ x fir)) (pp+ (butfirst sen)))))))This looks nice, except one might worry about efficiency, in that shared partial sums are not being used, so it might take too much time to compute.
(define (pp+ sen) (if (empty? sen) '() (let ((partial-pre (pp+ (butlast sen)))) (sentence partial-pre (+ (last sen) (if (empty? partial-pre) 0 (last partial-pre)))))))This seems elegant, in that it uses the already-computed total for the first part of the sentence in calculating the remaining numbers, so it will not do too many additions. But it might be inefficient for a different reason: first is fast, while last takes time of order the length of the sentence, as does tacking a new element onto the end of a sentence.
(define (pp+ sen) (pp+-aux 0 sen)) (define (pp+-aux running-total sen) (if (empty? sen) '() (let ((new-running-total (+ running-total (first sen)))) (sentence new-running-total (pp+-aux new-running-total (butfirst sen))))))Which is efficient both in its manipulation of the sentence and in taking advantage of the left-to-right structure of the addition.
Let's look at it's call graph for (pp+ '(1000 200 30 4))
Each element in the sequence is the sum of the previous two elements: 1, 1, 2, 3, 5, 8, 13, 21, 34, etc.
We want to define our function such that
(fibb 0) ; 1 (fibb 1) ; 1 (fibb 2) ; 2 (fibb 3) ; 3 (fibb 4) ; 5 (fibb 5) ; 8 (fibb 6) ; 13 (fibb 7) ; 21 (fibb 8) ; 34The obvious recursive definition is:
(define (fibb n) (cond ((= n 0) 1) ((= n 1) 1) (else (+ (fibb (- n 1)) (fibb (- n 2))))))This will be very inefficient for large arguments: the total number of recursive calls is going up exponentially with the argument! We can see that by sketching the call graph of (fibb 8)
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 last two Fibonacci numbers, and the number of steps that remain to be taken.
(define (fibb n) (fibb-aux 1 1 (- n 1))) (define (fibb-aux 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 8)