to define the function
formulate a Scheme expression for each simple cond-line explain for all other cond-clauses what each natural recursion computes according to the purpose statement
Test to discover mistakes (``typos'' and logic)
apply the function to the inputs of the examples check that the outputs are as predicted
Figure 26: Designing a function for self-referential data (Refines the recipes in figures 4 (pg. 5), 12 (pg. 9), and 18 (pg. 10))
9.5
More on Processing Simple Lists
Let us now look at another aspect of inventory management: the cost of an inventory. In addition to a list of the available toys, a store owner should also maintain a list of the cost of each item. The cost list permits the owner to determine how much the current inventory is worth or, given the inventory at the beginning of the year and that of the end of the year, how much profit the store makes.
A list of costs is most easily represented as a list. For example:
empty
(cons 1.22 empty) (cons 2.59 empty)
(cons 1.22 (cons 2.59 empty))
(cons 17.05 (cons 1.22 (cons 2.59 empty)))
Again, for a real store, we cannot place an arbitrary limit on the size of such a list, and functions that process such cost lists must be prepared to consume lists of arbitrary size.
Suppose the toy store needs a function that computes the value of an inventory from the cost of the individual toys. We call this function sum. Before we can define sum, we must figure out how to describe all possible lists of numbers that the function may consume. In short, we need a data definition that precisely defines what an arbitrarily large list of numbers is. We can obtain this definition by replacing ``symbol'' with
``number'' in the definition of lists of symbols:
A list-of-numbers is either
1. the empty list, empty, or
2. (cons n lon) where n is a number and lon is a list of numbers.
Given that this data definition is self-referential again, we must first confirm that it actually defines some lists and that it defines all those inventories that we wish to represent. All of the examples above are lists of numbers. The first one, empty, is included explicitly. The second and third are constructed by adding the numbers 1.22
and 2.59, respectively, to the empty list. The others are lists of numbers for similar reasons.
As always, we start the development of the function with a contract, header, and purpose statement:
;; sum : list-of-numbers -> number
;; to compute the sum of the numbers on a-list-of-nums (define (sum a-list-of-nums) ...)
Then we continue with function examples:
(= (sum empty) 0)
(= (sum (cons 1.00 empty)) 1.0)
(= (sum (cons 17.05 (cons 1.22 (cons 2.59 empty)))) 20.86)
If sum is applied to empty, the store has no inventory and the result should be 0. If the input is (cons 1.00 empty), the inventory contains only one toy, and the cost of the toy is the cost of the inventory. Hence the result is 1.00. Finally, for (cons 17.05 (cons 1.22 (cons 2.59 empty))), sum should yield
For the design of sum's template, we follow the design recipe, step by step. First, we add the cond-expression:
(define (sum a-list-of-nums) (cond
[(empty? a-list-of-nums) ...]
[(cons? a-list-of-nums) ...]))
The second clause indicates with a comment that it deals with constructed lists. Second, we add the appropriate selector expressions for each clause:
(define (sum a-list-of-nums) (cond
[(empty? a-list-of-nums) ...]
[(cons? a-list-of-nums)
... (first a-list-of-nums) ... (rest a-list-of-nums) ...]))
Finally, we add the natural recursion of sum that reflects the self-reference in the data definition:
(define (sum a-list-of-nums) (cond
[(empty? a-list-of-nums) ...]
[else ... (first nums) ... (sum (rest a-list-of-nums)) ...]))
The final template reflects almost every aspect of the data definition: the two clauses, the construction in the second clauses, and the self-reference of the second clauses. The only part of the data definition that the function template does not reflect is that the first item of a constructed input is a number.
Now that we have a template, let us define the answers for the cond-expression on a clause-by-clause basis. In the first clause, the input is empty, which means that the store has no inventory. We already agreed that in this case the inventory is worth nothing, which means the corresponding answer is 0. In the second clause of the template, we find two expressions:
1. (first a-list-of-nums), which extracts the cost of the first toy; and
2. (sum (rest a-list-of-nums)), which, according to the purpose statement of
sum, computes the sum of (rest a-list-of-nums).
From these two reminders of what the expressions already compute for us, we see that the expression
(+ (first a-list-of-nums) (sum (rest a-list-of-nums)))
computes the answer in the second cond-clause.
Here is the complete definition of sum:
(define (sum a-list-of-nums) (cond
[(empty? a-list-of-nums) 0]
[else (+ (first nums) (sum (rest a-list-of-nums)))]))
A comparison of this definition with the template and the data definition shows that the step from the data definition to the template is the major step in the function
development process. Once we have derived the template from a solid understanding of the set of possible inputs, we can focus on the creative part: combining values. For simple examples, this step is easy; for others, it requires rigorous thinking.
We will see in future sections that this relationship between the shape of the data definition and the function is not a coincidence. Defining the class of data that a function consumes always determines to a large extent the shape of the function.
Exercise 9.5.1. Use DrScheme to test the definition of sum on the following sample lists of numbers:
empty
(cons 1.00 empty)
(cons 17.05 (cons 1.22 (cons 2.59 empty)))
Compare the results with our specifications. Then apply sum to the following examples:
empty
(cons 2.59 empty)
(cons 1.22 (cons 2.59 empty))
First determine what the result should be; then use DrScheme to evaluate the expressions. Solution
Exercise 9.5.2. Develop the function how-many-symbols, which consumes a list of symbols and produces the number of items in the list.
Develop the function how-many-numbers, which counts how many numbers are in a list of numbers. How do how-many-symbols and how-many-numbers differ? Solution
Exercise 9.5.3. Develop the function dollar-store?, which consumes a list of prices (numbers) and checks whether all of the prices are below 1.
For example, the following expressions should evaluate to true:
(dollar-store? empty)
(not (dollar-store? (cons .75 (cons 1.95 (cons .25 empty))))) (dollar-store? (cons .15 (cons .05 (cons .25 empty))))
Generalize the function so that it consumes a list of prices (numbers) and a threshold price (number) and checks that all prices in the list are below the threshold.
Solution
Exercise 9.5.4. Develop the function check-range1?, which consumes a list of temperature measurements (represented as numbers) and checks whether all measurements are between 5oC and 95oC.
Generalize the function to check-range?, which consumes a list of temperature measurements and a legal interval and checks whether all measurements are within the legal interval. Solution
Exercise 9.5.5. Develop the function convert. It consumes a list of digits and produces the corresponding number. The first digit is the least significant, and so on.
Also develop the function check-guess-for-list. It implements a general version of the number-guessing game of exercise 5.1.3. The function consumes a list of digits, which represents the player's guess, and a number, which represents the randomly chosen and hidden number. Depending on how the converted digits relate to target,
check-guess-for-list produces one of the following three answers: 'TooSmall,
'Perfect, or 'TooLarge.
The rest of the game is implemented by guess.ss. To play the game, use the teachpack to guess.ss and evaluate the expression
(guess-with-gui-list 5 check-guess-for-list)
after the functions have been thoroughly developed. Solution
Exercise 9.5.6. Develop the function delta, which consumes two price lists, that is, lists of numbers. The first represents the inventory at the beginning of a time period, the second one the inventory at the end. The function outputs the difference in value. If the value of the inventory has increased, the result is positive; if the value has decreased, it is negative. Solution
Exercise 9.5.7. Define the function average-price. It consumes a list of toy prices and computes the average price of a toy. The average is the total of all prices divided by the number of toys.
Iterative Refinement: First develop a function that works on non-empty lists. Then produce a checked function (see section 7.5) that signals an error when the function is applied to an empty list. Solution
Exercise 9.5.8. Develop the function draw-circles, which consumes a posnp and a list of numbers. Each number of the list represents the radius of some circle. The function draws concentric red circles around p on a canvas, using the operation draw-circle. Its result is true, if it can draw all of them; otherwise an error has occurred and the function does not need to produce a value.
Use the teachpack draw.ss; create the canvas with (start 300 300). Recall that
draw.ss provides the structure definition for posn (see section 7.1). Solution
29 The traditional names are car and cdr, but we will not use these nonsensical names.
30 It is common that a data definition describes a class of data that contains more than the intended elements. This limitation is inherent and is just one of the many symptoms of the limits of computing.
31 Numbers also seem to be arbitrarily large. For inexact numbers, this is an illusion. For precise integers, this is indeed the case, and we will discuss them later in this part.