Structures are one way to represent compound information. They are useful when we know how many pieces of data we wish to combine. In many cases, however, we don't know how many things we wish to enumerate, and in that case we form a list. A list can be of arbitrary length, that is, it contains a finite, but undetermined number of pieces of data.
Forming lists is something that all of us do. Before we go grocery shopping, we often write down a list of items that we want to purchase. When we plan out a day in the morning, we write down a list of things to do. During December, many children prepare Christmas wish lists. To plan a party, we list the people we want to invite. In short, arranging information in the form of lists is a ubiquitous part of our life, and we should learn to represent lists as Scheme data. In this section, we first learn to create lists and then move on to developing functions that consume lists.
9.1
Lists
When we form a list, we always start out with the empty list. In Scheme,
empty
represents the empty list. From here, we can construct a longer list with the operation
cons. Here is a simple example:
(cons 'Mercury empty)
In this example, we constructed a list from the empty list and the symbol 'Mercury. Figure 25 presents this list in the same pictorial manner we used for structures. The box
for cons has two fields: first and rest. In this specific example the first field contains 'Mercury and the rest field contains empty.
(cons 'Mercury empty) 'Mercury empty
(cons 'Venus (cons 'Mercury empty)) 'Venus 'Mercury empty (cons 'Earth (cons 'Venus (cons 'Mercury
empty))) 'Earth 'Venus 'Mercury empty
Figure 25: Building a list
Once we have a list with one item on it, we can construct lists with two items by using
cons again:
(cons 'Venus (cons 'Mercury empty))
The middle row of figure 25 shows how we should imagine the second list. It is also a box of two fields, but this time the rest field contains a box. Indeed, it contains the box from the top row of the same figure.
Finally, we construct a list with three items:
(cons 'Earth (cons 'Venus (cons 'Mercury empty)))
The last row of figure 25 illustrates the list with three items. Its rest field contains a box that contains a box again. So, as we create lists we put boxes into boxes into boxes, etc. While this may appear strange at first glance, it is just like a set of Chinese gift boxes or a set of nested drinking cups, which we sometimes get for our early birthdays.
The only difference is that Scheme programs can nest lists much deeper than any artist could nest physical boxes.
Exercise 9.1.1. Create Scheme lists that represent
1. the list of all planets in our solar system;
2. the following meal: steak, pommes-frites, beans, bread, water, juice, brie-cheese, and ice-cream; and
3. the list of basic colors.
Sketch box representations of these lists, similar to those in figure 25. Solution
We can also make lists of numbers. As before, empty is the list without any items. Here
To build it requires 10 list constructions and one empty list.
In general a list does not have to contain values of one kind, but may contain arbitrary values:
(cons 'RobbyRound (cons 3
(cons true empty)))
Here the first item is a symbol, the second one is a number, and the last one a boolean.
We could think of this list as the representation of a personnel record that includes the name of the employee, the number of years spent at the company, and whether the employee has health insurance through the company plan.
Now suppose we are given a list of numbers. One thing we might wish to do is add up the numbers on the list. To make this more concrete, let us assume that we are only interested in lists of three numbers:
A list-of-3-numbers is
(cons x (cons y (cons z empty))) where x, y, and z are numbers.
We write down the contract, purpose, header, and examples as before:
;; add-up-3 : list-of-3-numbers -> number
;; to add up the three numbers in a-list-of-3-numbers
;; examples and tests:
;; (= (add-up-3 (cons 2 (cons 1 (cons 3 empty)))) 6)
;; (= (add-up-3 (cons 0 (cons 1 (cons 0 empty)))) 1) (define (add-up-3 a-list-of-3-numbers) ...)
To define the body, however, presents a problem. A constructed list is like a structure.
Hence we should layout a template with selector expressions next. Unfortunately, we don't know how to select items from a list.
In analogy to structure selectors, Scheme implements operations for extracting the fields from a constructed list: first and rest.29 The first operation extracts the item that we used to construct a list; the rest operation extracts the list field.
To describe how first, rest, and cons are related, we can use equations that are similar to the equations that govern addition and subtraction and structure creation and field extraction:
( first ( cons 10 empty ))
= 10
( rest ( cons 10 empty ))
= empty
(first ( rest ( cons 10 ( cons 22 empty ))) )
= ( first ( cons 22 empty ))
= 22
The last one demonstrates how to evaluate nested expressions. The key is to think of
(cons a-value a-list) as a value. And, as always, we start with the evaluation of the innermost parenthesized expressions that can be reduced, just as in arithmetic. In the above calculations, the expressions that are about to be reduced next are underlined.
Using first and rest we can now write down a template for add-up-3:
;; add-up-3 : list-of-3-numbers -> number
;; to add up the three numbers in a-list-of-3-numbers (define (add-up-3 a-list-of-3-numbers)
... (first a-list-of-3-numbers) ...
... (first (rest a-list-of-3-numbers)) ...
... (first (rest (rest a-list-of-3-numbers))) ... )
The three expressions remind us that the input, called a-list-of-3-numbers, contains three components and how to extract them.
Exercise 9.1.2. Let l be the list
(cons 10 (cons 20 (cons 5 empty)))
What are the values of the following expressions?
1. (rest l)
2. (first (rest l))
3. (rest (rest l))
4. (first (rest (rest l)))
5. (rest (rest (rest l))) Solution
Exercise 9.1.3. Finish the development of add-up-3, that is, define the body and test the complete function on some examples.
A list of three numbers is one possible representation for 3-dimensional points. The distance of a 3-dimensional point to the origin of the coordinate grid is computed in the same manner as that of 2-dimensional point: by squaring the numbers, adding them up, and taking the square root.
Use the template for add-up-3 to develop distance-to-0-for-3, which computes the distance of a 3-dimensional point to the origin. Solution
Exercise 9.1.4. Provide a data definition for lists of two symbols. Then develop the function contains-2-doll?, which consumes a list of two symbols and determines whether one of them is 'doll. Solution
On the Precise Relationship between Cons and Structures: The discussion of cons,
first, and rest suggests that cons creates a structure and first and rest are ordinary selectors:
(define-struct pair (left right))
(define (our-cons a-value a-list) (make-pair a-value a-list)) (define (our-first a-pair) (pair-left a-pair))
(define (our-rest a-pair) (pair-right a-pair)) (define (our-cons? x) (pair? x))
Although these definitions are a good first approximation, they are inaccurate in one important point. DrScheme's version of cons is really a checked version of make-pair. Specifically, the cons operation ensures that the right field is always a list, that is,
constructed or empty. This suggests the following refinement:
(define (our-cons a-value a-list) (cond
[(empty? a-list) (make-pair a-value a-list)]
[(our-cons? a-list) (make-pair a-value a-list)]
[else (error 'our-cons "list as second argument expected")]))
The definitions for our-first, our-rest, and our-cons? remain the same. Finally, we must also promise not to use make-pair directly so that we don't accidentally build a bad list.