Recall the function annotate from Session 9. It takes a flat list of symbols, such as (ball-state michigan-state northern-iowa), and returns a list of symbol/position lists, ((ball-state 1) (michigan-state 2) (northern-iowa 2)). Solving this problem required a new idea, the interface procedure.
Then we learned about a new data type, the s-list, which allowed symbols in the list to be nested inside of other lists. S-lists are useful to us because they resemble Racket programs, which means that learning how to process s-lists helps us learn how to process programs of this form.
Can we annotate an s-list? Sure. Instead of a symbol's position in any particular list, we would probably care more about its depth in the tree. For example:
> (annotate-slist '(ball-state michigan-state northern-iowa)) '((ball-state 0) (michigan-state 0) (northern-iowa 0)) > (annotate-slist '((a b) (((b g r) (f r)) c (d e)) b)) '(((a 1) (b 1)) ((((b 3) (g 3) (r 3)) ((f 3) (r 3))) (c 1) ((d 2) (e 2))) (b 0))
Try to write annotate-slist now. This problem is a straight application of three techniques we have studied: structural recursion, mutual recursion, and interface procedures. Then scroll down to see how I solved the problem.
. Spoilers below.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Let's start with an interface procedure and a data-driven outline of the helper that handles s-lists:
(define annotate-slist (lambda (slst) (annotate-counted slst 0))) (define annotate-counted (lambda (slst level) (if (null? slst) ; handle empty list ; handle cons )))
The empty list case is easy to handle: the result is also the empty list. The cons case is almost as easy: annotate the car, annotate the cdr, and return a new cons of the results:
(define annotate-counted (lambda (slst level) (if (null? slst) '() (cons (... annotate first ...) (... annotate rest ...)))))
The rest is an s-list, so a call to annotate-counted will do the trick. The first is a symbol expression, so let's call a helper:
(define annotate-counted (lambda (slst level) (if (null? slst) '() (cons (annotate-sym-expr (first slst) level) (annotate-counted (rest slst) level))))) (define annotate-sym-expr (lambda (sym-expr level) (if (symbol? sym-expr) ; handle symbol ; handle slst)))
We're almost there. Annotating a symbol is easy: we return a 2-list of the symbol and the count. Annotating an s-list isn't much harder, because we just wrote annotate-counted! All we have to do is record the fact that we are going one level deeper in the structure. Here goes:
(define annotate-sym-expr (lambda (sym-expr level) (if (symbol? sym-expr) (list sym-expr level) (annotate-counted sym-expr (+ level 1)))))
The result is three procedures that follow from the three design patterns you've used several times over the last few weeks: structural recursion, interface procedure, and mutual recursion. The problem is a challenge but, again, if we proceed systematically, the patterns guide us home.