Skip to main content

Laboratory: List Recursion, Revisited

Summary: In this laboratory, you will continue to explore the use of recursion.

Reference

Preparation

a. Make a copy of list-recursion-revisited-lab.rkt, which contains useful definitions for this lab.

b. Review the file to see what procedures and values are in the list.

Exercises

Exercise 1: Finding the Largest

How can we find the largest value in the list of numbers? As you’ve seen, (max val1 val2) computes the largest of val1 ... val2. We want to generalize this procedure to work with a list of values.

Write (largest vals), a procedure that computes the largest value in a list of real numbers

To be as general as possible, your implementation of largest should handle negative as well as positive numbers.

> (largest (list 1 2 3 4))
4
> (largest (list 1 10 5 6.333 0))
10
> (largest (list -5 -10 -1 -99))
-1

Exercise 2: Joining Lists

You may recall that the procedure append takes as parameters two lists, and joins the two lists together. Let’s generalize that procedure so that it works with more than two lists.

a. Write a procedure, lists-join, that, given a nonempty list of lists as a parameter, joins the member lists together using append.

> (list (list 1 2 3))
'((1 2 3))
> (lists-join (list (list 1 2 3)))
'(1 2 3)
> (list (list 1 2 3) (list 10 11 12))
'((1 2 3) (10 11 12))
> (lists-join (list (list 1 2 3) (list 10 11 12)))
'(1 2 3 10 11 12)
> (list (list 1 2 3) (list 10 11 12) (list 20 21))
'((1 2 3) (10 11 12) (20 21))
> (lists-join (list (list 1 2 3) (list 10 11 12) (list 20 21)))
'(1 2 3 10 11 12 20 21)
> (list null (list 1 2 3))
'(() (1 2 3))
> (lists-join (list null (list 1 2 3)))
'(1 2 3)
> (list (list 1 2 3) null)
'((1 2 3) ())
> (lists-join (list (list 1 2 3) null))
'(1 2 3)
> (lists-join (list null (list 1 2 3) null null null null (list 100 99 98) null))
'(1 2 3 100 99 98)

Note: At first glance, it may be puzzling to work with a list of lists. However, you can disassemble that list just as you do any other list: the car of a list-of-lists is a list, the cdr of a list-of-lists is a list-of-lists, but with the first list removed.

Hint: Think about when you have a base case, what you do in the base case, and what to do with the result of the recursive case. (Remember, append is generally used to join two lists.)

b. Use lists-join to join a variety of lists.

Exercise 3: Folding

Here are possible answers for exercises 1 and 2.

(define largest
  (lambda (lst)
    (if (null? (cdr lst))
        (car lst)
        (max (car lst) (largest (cdr lst))))))

(define lists-join
  (lambda (lst)
    (if (null? (cdr lst))
        (car lst)
        (append (car lst) (lists-join (cdr lst))))))

You’ll notice that both do a similar thing: They take a two-parameter procedure (max or append) and generalize it to a list of values. The process of repeatedly applying a two-parameter procedure so as to process a list is often called folding the procedure. (Some call this process reducing the list using the procedure. You will hear that “map-reduce” is one of the core technologies of large-scale data analysis.)

You’ll also notice that they both lists-join and largest use similar code. When we identify a common structure for similar procedures, it can be helpful to generalize and then to explore that generalization. You will do so in this exercise.

a. Sketch a template of the common parts of lists-join and largest (with blanks to fill in for the rest).

b. Identify one or two other procedures from the reading that follow the same pattern.

c. Using your template, write a procedure, (smallest lst), that finds the smallest value in a list.

d. Using your template, write a procedure, (irgb-darkest lst), that finds the darkest color in a list of RGB colors.

Hint: You may find it useful to build a utility procedure, (irgb-darker-of irgb1 irgb2), that finds the darker of two colors.

Exercise 4: Checking for Brightness

You may recall that in the reading we explored ways to build predicates that apply to lists by starting with predicates that apply to individual values. Let’s try writing a few such procedures.

a. Write a procedure, (irgb-all-bright? colors), that, given a list of RGB colors, determines if all of the colors are bright. You should try using recursion with and and or. You should not use the all? procedure.

b. Write a procedure, (irgb-any-bright? colors), that, given a list of RGB colors, determines if any of them are bright. Yuu should try using recursion with and and or. You should not use the any? procedure.

Exercise 5: Checking for Membership

It is often useful to ask whether a list contains a particular item. In fact, the standard Scheme procedure member and the MediaScheme procedure member? both do this. Let’s write our own predicate, (list-contains? vals val) that holds only when val appears in vals.

We know that

  • val does not appear in the empty list.
  • val appears in a non-empty list, vals, if val is the car of vals
  • val appears in a non-empty list, vals, if val appears in the cdr of vals.

a. Translate this description into Scheme. That is, write list-contains?.

b. We can use list-contains? to define the following not-so-interesting procedure.

(define small-primes 
  (list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 51 53 59 61 67 71))
(define small-prime? (l-s list-contains? small-primes))

Explain what this procedure does.

c. What result do you expect from the following expressions?

> (map small-prime? (iota 20))
> (map small-prime? small-primes)
> (map small-prime? (map square (iota 10)))

d. Check your answers experimentally.

For Those With Extra Time

Extra 1: The Darkest Color Name

Using your template from Exercise 3, write a procedure, (color-name-darkest lst), that finds the darkest color in a list of color names. (You’ll need to convert color names to integer-encoded RGB colors in order to compare them. However, you should return a color name, not an RGB color.)

Hint: Again, you’ll find it useful to write a utility procedure, such as color-name-darker.

Extra 2: Closest to Zero

Using your template from Exercise 3, write a procedure, (closest-to-zero lst), that, given a list of positive and negative numbers, finds the number in the list closest to zero.

Extra 3: Averaging Colors

We’ve seen how to average two colors and a list of colors. But what if we want to do something different: Given a list of colors, we want averages, but only of neighboring elements in the list.

Write a procedure, (irgb-averages colors), that, given a list of colors, computes a new list of colors, by averaging subsequent pairs of colors. For example, if the input list is the standard seven rainbow colors (red, orange, yellow, green, blue, indigo, and violet), the output list will consist of a red-orange average, an orange-yellow average, a yellow-green average, a green-blue average, a blue-indigo average, and an indigo-violet average.

Once again, the length of the result list is one less than the length of the input list.

Extra 4: Splitting Lists

Define and carefully test a Scheme procedure, (unriffle lst), that takes a list as argument and returns a list of two lists, one comprising the elements in even-numbered positions in the given list, the other comprising the elements in odd-numbered-positions. For example:

> (unriffle (list 'a 'b 'c 'd 'e 'f 'g 'h 'i))
'((a c e g i) (b d f h))
> (unriffle null)
'(() ())
> (unriffle (list 'a))
'((a) ())
> (unriffle (list 'a 'b))
'((a) (b))