Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)

Laboratory: Recursion with Helper Procedures


Summary: In this laboratory, you will explore recursive techniques in which we pass along intermediate computations, often using a recursive helper procedure. When this technique is used in conjunction with a program structure in which the recursive result is returned directly (without accumulated actions to perform), this technique supports tail recursion.

Reference

Here are some of the important procedures from the reading.

;;; Procedure:
;;;   new-sum
;;; Parameters:
;;;   numbers, a list of numbers.
;;; Purpose:
;;;   Find the sum of the elements of a given list of numbers
;;; Produces:
;;;   total, a number.
;;; Preconditions:
;;;   All the elements of numbers must be numbers.
;;; Postcondition:
;;;   total is the result of adding together all of the elements of numbers.
;;;   If all the values in numbers are exact, total is exact.
;;;   If any values in numbers are inexact, total is inexact.
(define new-sum
  (lambda (numbers)
    (new-sum-helper 0 numbers)))

;;; Procedure:
;;;   new-sum-helper
;;; Parameters:
;;;   sum-so-far, a number.
;;;   remaining, a list of numbers.
;;; Purpose:
;;;   Add sum-so-far to the sum of the elements of a given list of numbers
;;; Produces:
;;;   total, a number.
;;; Preconditions:
;;;   All the elements of remaining must be numbers.
;;;   sum-so-far must be a number.
;;; Postcondition:
;;;   total is the result of adding together sum-so-far and all of the
;;;     elements of remaining.
;;;   If both sum-so-far and all the values in remaining are exact,
;;;     total is exact.
;;;   If either sum-so-far or any values in remaining are inexact,
;;;     total is inexact.
(define new-sum-helper
  (lambda (sum-so-far remaining)
     (if (null? remaining)
         sum-so-far
         (new-sum-helper (+ sum-so-far (car remaining))
                         (cdr remaining)))))

;;; Procedure:
;;;   difference
;;; Parameters:
;;;   lst, a list of numbers of the form (v1 v2 ... vn)
;;; Purpose:
;;;   Compute the difference of the values.
;;; Produces:
;;;   result, a number
;;; Preconditions:
;;;   lst is nonempty
;;;   lst contains only real numbers
;;; Postconditions:
;;;   result = v1 - v2 - v3 - ... - vn
(define difference
  (lambda (lst)
    (if (null? lst)
        0
        (- (car lst) (difference (cdr lst))))))

(define new-difference-helper
  (lambda (difference-so-far remaining)
    (if (null? remaining)
        difference-so-far
        (new-difference-helper (- difference-so-far (car remaining))
                               (cdr remaining)))))

(define new-difference
  (lambda (lst)
    (new-difference-helper 0 lst)))

(define newer-difference
  (lambda (lst)
    (new-difference-helper (car lst) (cdr lst))))

(define annotated-difference
  (lambda (lst)
    (annotated-difference-helper (car lst) (cdr lst))))
(define annotated-difference-helper
  (lambda (difference-so-far remaining)
    (write (list 'difference-helper difference-so-far remaining)) (newline)
    (if (null? remaining)
        difference-so-far
        (annotated-difference-helper (- difference-so-far (car remaining))
                                     (cdr remaining)))))

;;; Procedure:
;;;   rgb-brightness
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Computes the brightness of color on a 0 (dark) to 100 (light) scale.
;;; Produces:
;;;   b, an integer
;;; Preconditions:
;;;   color is a valid RGB color.  That is, each component is between
;;;     0 and 255, inclusive.
;;; Postconditions:
;;;   If color1 is likely to be perceived as lighter than color2,
;;;     then (brightness color1) > (brightness color2).
(define rgb-brightness
  (lambda (color)
    (round (* 100 (/ (+ (* 0.30 (rgb-red color))
                        (* 0.59 (rgb-green color))
                        (* 0.11 (rgb-blue color)))
                      255)))))

We've left the various versions of rgb-brightest for later.

Preparation

a. Add the definitions above to your definitions pane.

b. Create a list of twelve or so RGB colors and call it my-colors. For example,

(define my-color-names 
  (list "blood red" "cobalt blue" "dark forest green" "goldenrod"
        "hot pink" "light steel blue" "light wood" "nectarine"
        "Oregon salmon" "pencil lead" "periwinkle" "turquoise"))
(define my-colors
  (map cname->rgb my-color-names))

c. Create a few lists of shades of grey as follows:

(define greys-4
  (map (lambda (n) (rgb-new (* 64 n) (* 64 n) (* 64 n)))
       (list 4 3 2 1)))
(define greys-8
  (map (lambda (n) (rgb-new (* 32 n) (* 32 n) (* 32 n)))
       (list 8 7 6 5 4 3 2 1)))
(define greys-16
  (map (lambda (n) (rgb-new (* 16 n) (* 16 n) (* 16 n)))
       (list 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1)))

Exercises

Exercise 1: Exploring The Basic Procedures

a. Test new-sum, difference, new-difference, and newer-difference, to determine whether or not they behave as they should.

b. Using annotated-difference, observe the steps involved in computing the difference of the values in the list (list 1 2 1 2 1 2 1).

Exercise 2: Product, Revisited

a. Rewrite the product procedure, which computes the product of a list of values, using the same technique used for new-sum.

b. Write a similar my-quotient procedure. (Do not call it quotient, which is a built-in procedure that is commonly used.)

Exercise 3: Summing Red Components, Revisited

In the previous lab, you wrote a procedure, sum-red, that sums all the red components in a list of colors. Rewrite that procedure using the technique of carrying along intermediate values. Your procedure should look something like the following:

(define sum-red
  (lambda (colors)
     (sum-red-helper 0 colors)))
(define sum-red-helper 
  (lambda (sum-so-far remaining-colors)
    ...))

Exercise 4: Filtering, Revisited

a. Print out the RGB values in your list of colors with

(map rgb->string my-colors)

b. Here's a procedure using the “results so far” technique that filters out any colors with a red component of at least 128.

(define rgb-filter-out-high-red
  (lambda (colors)
    (rgb-filter-out-high-red-helper null colors)))
(define rgb-filter-out-high-red-helper
  (lambda (colors-so-far remaining-colors)
    (cond
      ((null? remaining-colors)
       colors-so-far)
      ((<= 128 (rgb-red (car remaining-colors)))
       (rgb-filter-out-high-red-helper colors-so-far (cdr remaining-colors)))
      (else
       (rgb-filter-out-high-red-helper
         (cons (car remaining-colors) colors-so-far)
         (cdr remaining-colors))))))

c. What do you expect this procedure to do when given the empty list? Check your answer experimentally.

d. What do you expect this procedure to do when given the list of the color white?

(rgb-filter-out-high-red (list color-white))

Check your answer experimentally.

e. What do you expect this procedure to do when given the list of the color black?

(rgb-filter-out-high-red (list color-black))

Check your answer experimentally.

f. What do you expect this procedure to do when given the list of the color blue?

(rgb-filter-out-high-red (list color-blue))

Check your answer experimentally.

g. What do you expect this procedure to do when given the list of the colors white, black, blue, yellow, green?

(map rgb->string (rgb-filter-out-high-red (list color-red color-green color-blue color-yellow color-black color-white)))

Check your answer experimentally.

h. What do you expect this procedure to do when given your list of colors as a parameter?

(map rgb->string (rgb-filter-out-high-red my-colors))

Check your answer experimentally.

i. In at least one case above, you should have received a somewhat strange result. Do your best to explain that result (ask your instructor or a TA if you're confused). Then, fix the code so that the result is not so strange.

Exercise 5: Counting Steps

Consider the following procedure.

(define turtle-step!
  (lambda (turtle)
    (turtle-forward! turtle 100)
    (turtle-turn! turtle 180)
    (turtle-forward! turtle 100)
    (turtle-turn! turtle 180)
    (turtle-turn! turtle 7)))

a. What do you expect the following sequence of operations to do?

> (define canvas (image-new 200 200))
> (image-show canvas)
> (define counter (turtle-new canvas))
> (turtle-teleport! counter 100 100)
> (turtle-step! counter)
> (turtle-step! counter)
> (turtle-step! counter)

b. Check your answer experimentally.

c. Reset the turtle and the drawing with

> (image-select-all! canvas)
> (image-clear-selection! canvas)
> (image-select-nothing! canvas)
> (context-update-displays!)
> (turtle-face! counter 0)

d. Now, let's use this procedure to count steps in the various versions of rgb-brightest. We'll start with the first version.

(define rgb-brightest
  (lambda (colors)
    (cond
      ((null? (cdr colors))
       (car colors))
      ((>= (rgb-brightness (car colors)) 
	   (rgb-brightness (rgb-brightest (cdr colors))))
       (car colors))
      (else
       (rgb-brightest (cdr colors))))))

Add the line (turtle-step! counter) immediately before the cond statement (that is, as the first line).

e. How many lines do you expect that the turtle will draw in the following call?

> (rgb-brightest greys-4)

Check your answer experimentally.

f. How many lines do you expect that the turtle will draw in the following call?

> (rgb-brightest greys-8)

Check your answer experimentally.

g. How many lines do you expect that the turtle will draw in the following call?

> (rgb-brightest greys-16)

Check your answer experimentally.

h. How many lines do you expect that the turtle will draw in the following call?

> (define grays-4 (reverse greys-4))
> (rgb-brightest grays-4)

Check your answer experimentally.

i. How many lines do you expect that the turtle will draw in the following call?

> (define grays-8 (reverse greys-8))
> (rgb-brightest grays-8)

Check your answer experimentally.

j. Explain why we didn't have you try the same steps again, using a reversed list of sixteen greys.

Exercise 6: Counting More Steps

Repeat the previous exercise using the helper version of rgb-brightest.

(define rgb-brightest
  (lambda (colors)
    (rgb-brightest-helper (car colors) (cdr colors))))
(define rgb-brightest-helper
  (lambda (brightest-so-far colors-remaining)
    (if (null? colors-remaining)
        brightest-so-far
        (rgb-brightest-helper
         (if (>= (rgb-brightness brightest-so-far) 
                 (rgb-brightness (car colors-remaining)))
             brightest-so-far
             (car colors-remaining))
         (cdr remaining-colors)))))

For Those With Extra Time

Extra 1: Summing Multiple Components, Simultaneously

Although we've primarily used helpers to keep track of one intermediate result, we can certainly pass along more than one intermediate result. For example, in averaging a list of colors, we can keep track of the sum of reds, the sum of greens, the sum of blues, and the count of colors. In the end, we can build a new color from these computed values.

(define rgb-average
  (lambda (colors)
    (rgb-average-helper 0 0 0 0 colors)))
(define rgb-average-helper
  (lambda (red-so-far green-so-far blue-so-far count remaining-colors)
    (if (null? remaining-colors)
        (rgb-new (/ red-so-far count)
                 (/ green-so-far count)
                 (/ blue-so-far count))
        (average-helper ...))))

a. Fill in the remaining code in the recursive call.

b. Test this code to average white and black.

(rgb->string (rgb-average (list color-white color-black)))

c. Test this code on a few other colors of your choice.

d. What do you expect to have happen if you provide rgb-average with the empty list?

e. Check your answer experimentally.

Extra 2: Too Many Ways to Compute the Brightest Color

The reading provides at least four ways to compute the brightest color in a list. Which do you prefer, and why?

Extra 3: Yet Another Way To Compute the Brightest Color

Let's consider the first version of rgb-brightest.

(define rgb-brightest
  (lambda (colors)
    (if (null? (cdr colors))
        (car colors)
        (if (>= (rgb-brightness (car colors)) 
                (rgb-brightness (rgb-brightest (cdr colors))))
            (car colors)
            (rgb-brightest (cdr colors))))))

Suppose we used let to bind names to the car and the result of the recursive call

(let ((candidate (rgb-brightest (cdr colors)))
      (alternate (car colors)))
  ...)

a. Rewrite this version of rgb-brightest to use these names in place of the values they name.

b. What effect do you expect this naming to have on the efficiency of the procedure?

c. Check your answer experimentally.

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)

This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.