Here is a third problem statement, given as in the form of a function contract, purpose statement, and header:
;; list-pick : list-of-symbols N[>= 1] -> symbol ;; to determine the nth symbol from alos, counting from 1; ;; signals an error if there is no nth item (define (list-pick alos n) ...)That is, the problem is to develop a function that a natural number and a list of symbols. Both belong to classes with complex data definitions, though, unlike for the previous two problems, the classes are distinct. Figure
The data definitions:
A natural number [>= 1] (N[>= 1]) is either
- 1 or
- (add1 n) if n is a N[>= 1].
A list-of-symbols is either
- the empty list, empty, or
- (cons s lof) where s is a symbol and lof is a list of symbols.
Because the problem is non-standard, we should ensure that our examples cover all important cases. We usually accomplish this goal by picking one item per clause in the definition and choosing elements from basic forms of data on a random basis. In this example, this procedure implies that we pick at least two elements from list-of-symbols and two from N[>= 1]. Let's choose empty and (cons 'a empty) for the former, and 1 and 3 for the latter. But two choices per argument means four examples total; after all, there is no immediately obvious connection between the two arguments and no restriction in the contract:
(list-pick empty 1) ;; expected behavior: (error 'list-pick ``...'')
(list-pick (cons 'a empty) 1) ;; expected value: 'a
(list-pick empty 3) ;; expected behavior: (error 'list-pick ``...'')
(list-pick (cons 'a empty) 3) ;; expected value: falseOnly one of the four results is a symbol; in the other cases, we see an error, indicating that the list doesn't contain enough items.
The discussion on examples indicates that there are indeed four possible, independent cases that we must consider for the design of the function. We can discover the four cases by arranging the necessary conditions in a table format:
The horizontal dimension of the table lists those questions that list-pick must ask about the list argument; the vertical dimension lists the questions about the natural number. Furthermore, the partitioning of the table yields four squares. Each square represents the case when both the condition on the horizontal and the one on the vertical are true. We can express this fact with and-expressions in the squares:
| (empty? alos) | (cons? alos) | |
| (= n 1) |
|
|
| (> n 1) |
|
|
It is straightforward to check that for any given pair of arguments exactly one of the four composite claims must evaluate to true.
Using our cases analysis, we can now design the first part of the template, the conditional expression:
(define (list-pick alos n)
(cond
[(and (= n 1) (empty? alos)) ...]
[(and (> n 1) (empty? alos)) ...]
[(and (= n 1) (cons? alos)) ...]
[(and (> n 1) (cons? alos)) ...]))
The cond-expression asks all four questions, thus distinguishing
all possibilities. Next we must add selector expressions to each
cond-clause if possible:
(define (list-pick n alos)
(cond
[(and (= n 1) (empty? alos))
...]
[(and (> n 1) (empty? alos))
... (sub1 n) ...]
[(and (= n 1) (cons? alos))
... (first alos) ... (rest alos)...]
[(and (> n 1) (cons? alos))
... (sub1 n) ... (first alos) ... (rest alos) ...]))
For n, a natural number, the template contains at most one
selector expression, which determines the predecessor of n. For
alos, it might contain two. In those cases where either (= n 1) or (empty? alos) holds, one of the two arguments is atomic
and there is no need for a corresponding selector expression.
The final step of the template construction demands that we annotate the template with recursions where the results of selector expressions belong to the same class as the inputs. In the template for list-pick, this makes sense only in the last cond-clause, which contains both a selector expression for N[>= 1] and one for list-of-symbols. All other clauses contain at most one relevant selector expression. It is, however, unclear how to form the natural recursions. If we disregard the purpose of the function, and the template construction step asks us to do just that, there are three possible recursions:
;; list-pick : list-of-symbols N[>= 1] -> symbol ;; to determine the nth symbol from alos, counting from 1; ;; signals an error if there is no nth item (define (list-pick n alos) (cond [(and (= n 1) (empty? alos)) (error 'list-pick ``list too short'')] [(and (> n 1) (empty? alos)) (error 'list-pick ``list too short'')] [(and (= n 1) (cons? alos)) (first alos)] [(and (> n 1) (cons? alos)) (list-pick (rest alos) (sub1 n))]))
Following the design recipe, let us analyze each cond-clause in the template and decide what a proper answer is:
(list-pick (cons 'a (cons 'b empty)) 2)
The answer must be 'b, (first alos) is 'a, and (sub1 n) is 1. Here is what the three natural recursions would compute with these values:
Recall that, according to the purpose statement,
(list-pick (rest alos) (sub1 n))picks the (n-1)st item from (rest alos). In other words, for the second application, we have decreased the index by 1, shortened the list by one item, and now look for an item. Clearly, the second application always produces the same answer as the first one, assuming alos and n are ``compound'' values. Hence our choice for the last clause is truly justified.
Develop list-pick0, which picks items from a list like list-pick0 but starts counting at 0.
Examples:
(symbol=? (list-pick0 (list 'a 'b 'c 'd) 3)
'd)
(list-pick0 (list 'a 'b 'c 'd) 4) ;; expected behavior: (error 'list-pick0 ``the list is too short'')Solution