Held Monday, April 9, 2001
Today we revisit recursion and consider a more efficient way to write
recursive procedures. This new technique is called tail recursion.
- Anne is changing tonight's office hours to 7:30-8:30.
- For Wednesday, read
- As some of you have noted, I regularly make PDF files for readings and
labs. Does anyone use those? Does anyone want to?
- Are there questions on the exam?
- I'd recommend that you break problem 1 up into parts, and do each
- You can have a precondition in problem 1 that states the maximum
length of a first or last name. (Extra credit for handling an
- You may want to review the string procedures for hints on how
to do left- and right- justification.
- For problem 2, I want you to change the vector given
as a parameter, and not return a new vector.
- Here's what I expected for intersect:
(lambda (set1 set2)
((remove (complement (right-section member set2))) set1)))
- In English: Remove from set1 all values that are not in set 2.
- Why did I give this problem? Because I wanted you to see that
the higher-order procedures give us a very different way of thinking
about problem solving. Once you've written good helpers, your
first inclination no longer has to be one that involves a
standard recursive pattern.
- Kinds of recursion
- Why do tail recursion
- Generating lists tail-recursively
- Recursion in which you do something with the recursive result.
- E.g., definition of
- Recursion in which you simply return the recursive result, unchanged.
member?, the left-associative
- The latter kind of recursion is called tail recursion and,
in Scheme, is likely to be faster. Why?
- Let's look at the two versions of
sum (in class).
- Version 1, right associative, not tail recursive.
(if (null? values) 0
(+ (car values) (sumr (cdr values))))))
+ Version 2, left associative, tail recursive.
(suml-helper values 0)))
(lambda (remaining-values partial-sum)
(if (null? remaining-values) partial-sum
(suml-helper (cdr remaining-values)
- Let's sum the list
(1 2 3 4) and see if one seems
to be easier to deal with.
- Right associative
(sumr (1 2 3 4))
--> (+ 1 (sumr (2 3 4)))
--> (+ 1 (+ 2 (sumr (3 4))))
--> (+ 1 (+ 2 (+ 3 (sumr (4)))))
--> (+ 1 (+ 2 (+ 3 (+ 4 (sumr ())))))
--> (+ 1 (+ 2 (+ 3 (+ 4 0))))
--> (+ 1 (+ 2 (+ 3 4)))
--> (+ 1 (+ 2 7))
--> (+ 1 9)
- Left associative
(suml (1 2 3 4))
--> (suml-helper (1 2 3 4) 0)
--> (suml-helper (2 3 4) 1)
--> (suml-helper (3 4) 3)
--> (suml-helper (4) 6)
--> (suml-helper () 10)
- Which would you prefer to evaluate by hand?
- In the right-associative case, Scheme needs to remember the
things to do once the recursion reaches the base case.
- Remembering that stuff takes extra memory and time.
- Hence, tail-recursive procedures are normally quicker than
- The normal strategy for making procedures tail recursive is the
one we used for
- Make a helper procedure with one additional parameter.
- The additional parameter
accumulates partial solutions.
Hence, we often call it an accumulator.
- You can often transform a non-tail-recursive (but recursive) procedure
in to a tail-recursive procedure:
- Build that helper with an extra parameter.
- The accumulator normally starts out with the value from the
old base case (the one in the non-tail-recursive procedure).
- The operation used to update the accumulator is similar to
(but not necessarily identical to) the one used in the
standard recursive call.
- While tail recursion is generally a good thing, it's a bad idea
to spend lots of extra computational effort on building the
accumulated partial result.
- The problem appears most frequently in procedures that build
- Consider the problem of adding two to each value in a list.
- Suppose our accumulator contains all the values we've added
- Then at each step, we'll need to put the next value at the end
of the accumulator.
- How do you add to the end of a list?
- How much computational effort does it involve?
- You'll find that your procedure spends a lot of time walking
through the accumulated result.
- Can we do something better? Yes. Accumulate the
reverse of the desired result.
- Updating at each step is each; just cons to the front.
- We do need to spend some extra effort at the end to reverse
- Alternately, we can reverse the parameter before calling the
- See the reading
for more details.
Friday, 12 January 2001
- Created generic outline format for class.