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 3))
Solving this problem required a new idea: the interface procedure.
In that same session, 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 might 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 slst)
(annotate-counted slst 0))
(define (annotate-counted 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 first, annotate the
second, and return a new cons of the
results:
(define (annotate-counted 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 create a helper function and call it:
(define (annotate-counted slst level)
(if (null? slst)
'()
(cons (annotate-sym-expr (first slst) level)
(annotate-counted (rest slst) level))))
(define (annotate-sym-expr 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 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.