How to Annotate an S-List
Putting Several Patterns Together
An annotate
Function for s-lists
Recall
the annotate
function
from Session 10. 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 allows 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))
Writing annotate-slist
annotate-slist
now. This problem is an
application of three techniques we have studied:
structural recursion,
mutual recursion,
and
interface procedures.
When you are done, press the button to see a solution...
A Solution
As in the original annotate function, we know that we will need a counter to keep track of the nesting level, which we can use to annotate each symbol. We will have to pass that level on each recursive call. So let's start with an interface procedure and an outline of the helper that handles s-lists. The helper will follow the BNF description of an s-list:
(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 straightforward, too:
annotate the car
, annotate the cdr
, and
return a new cons
of the results:
(define annotate-counted (lambda (slst level) (if (null? slst) '() (cons (... annotatefirst
...) (... annotaterest
...)))))
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 create
a helper function and call it:
(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. We annotate a symbol by returning a 2-list
containing the symbol and the current level. If
sym-expr
is an s-list, we can annotate it by calling
annotate-counted
! All we have to do is record the
fact that we are going one level deeper in the structure, by adding
1 to the level. 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, if we proceed systematically, the patterns guide us home.
Here is a Racket file containing the entire solution, for your programming pleasure.