Given a deeply-nested list such as:
(a ((b c)) (d (e (f))) g)
it is often useful to visualise it as a tree written out as follows:
(a ((b c)) (d (e (f))) g) | ---------------------------------------- | | | | a ((b c)) (d (e (f))) g | | | ---------- | | | (b c) d (e (f)) | | ------ -------- | | | | b c e (f) | f
In order to explore this whole tree, that is to visit every sublist of a list, we need to use deep-recursion. A function is deeply recursive if it is called recursively on the car of a list as well as on its cdr.
(define (occurs_atom? x expr)
(cond
((atom? expr) (eqv? x expr))
(else (or
(occurs_atom? x (car expr))
(occurs_atom? x (cdr expr))
))
)
)
(example '(occurs_atom? 'a '(b (c d ((a))))) #t)
Instead of merely reporting on the existence of an atomic quantity in a
list-structure, we may want to report on how many times it occurs. The
above function is quite easily modified to do this.
(define (count_occurrences x expr)
(cond
((atom? expr) (if (eqv? x expr) 1 0))
(else (+
(count_occurrences x (car expr))
(count_occurrences x (cdr expr))
))
)
)
(example '(count_occurrences 'a '((b (c)) (a) (b ((a))))) 2)
Let us use trace to see how count_occurrences works.
(trace count_occurrences)
If we now call
(count_occurrences 'a '(b (a) (b (c a))))
we obtain the following print-out italics were inserted by the author.
(count_occurrences a (b (a) (b (c a))) ) top level call |(count_occurrences a b ) recursive call on car of list |count_occurrences = 0 a does not occur in b |(count_occurrences a ((a) (b (c a))) ) now do cdr of original list | (count_occurrences a (a) ) and do its car | |(count_occurrences a a ) and the car of that | |count_occurrences = 1 in which a occurs once. | |(count_occurrences a () ) and do the cdr of '(a) | |count_occurrences = 0 in which a does not occur | count_occurrences = 1 so a occurs once in '(a) | (count_occurrences a ((b (c a))) ) | |(count_occurrences a (b (c a)) ) | | (count_occurrences a b ) | | count_occurrences = 0 | | (count_occurrences a ((c a)) ) | | |(count_occurrences a (c a) ) | | | (count_occurrences a c ) | | | count_occurrences = 0 | | | (count_occurrences a (a) ) | | | |(count_occurrences a a ) | | | |count_occurrences = 1 | | | |(count_occurrences a () ) | | | |count_occurrences = 0 | | | count_occurrences = 1 | | |count_occurrences = 1 | | |(count_occurrences a () ) | | |count_occurrences = 0 | | count_occurrences = 1 | |count_occurrences = 1 | |(count_occurrences a () ) | |count_occurrences = 0 | count_occurrences = 1 |count_occurrences = 2 count_occurrences = 2
(define (subst x y expr)
(if
(atom? expr)
(if (eq? x expr) y expr)
(cons (subst x y (car expr))
(subst x y (cdr expr))
)
)
)
Consider the function flatten which takes a tree and makes a (flat) list of all the "tips" of the tree.
(define (flatten tree)
(cond
((null? tree) '())
((atom? (car tree)) (cons (car tree) (flatten (cdr tree))))
(else
(append (flatten (car tree))
(flatten (cdr tree)))))
)
(example '(flatten '((b (c)) (a) (b ((a))))) '(b c a b a))
Notice that flatten has complexity O(n^2) - it takes O(n) operations to do an append, and this is done O(n) times.
We can make a more efficient tree-flattening function using an accumulator. In the following definition, the accumulator ans can be regarded as a kind of basket. We crawl all over the tree, "picking cherries", that is the things we want to find in the tree, and putting them in the basket.
(define (flatten2 tree ans)
(cond
((null? tree) ans)
((atom? tree) (cons tree ans))
(else
(flatten2 (car tree) (flatten2 (cdr tree) ans)))
)
)
(define (flatten tree) (flatten2 tree '()))
(example '(flatten '((a b (c)) (d) (e ((f))))) '(a b c d e f))
(example '(subst 'a 3 '(+ (* a 7) b)) '(+ (* 3 7) b))
(define (reverse l)
(if (null? l)
'()
(append (reverse (cdr l)) (list (car l)))
)
)
(example '(reverse '(1 2 3)) '(3 2 1))
But we can make a more efficient version using accumulation thus:
(define (reverse l)
(reverse_1 l '())
)
(define (reverse_1 l acc)
(if (null? l)
acc
(reverse_1 (cdr l) (cons (car l) acc))
)
)
(reverse '(2 3 4))
(4 3 2)
Often in computing we want to set up a finite mapping that relates one finite set to another. For example we may want to represent the fact that a variable a has the value 1, b has the value 2 and c has the value 3. We can represent such a mapping using an association list or alist:
(define e '((a 1) (b 2) (c 3)))
In order to access and update such alists, Scheme provides 3 functions assoc, assq and assv. They all take two arguments. The first of these is a "key" and the second is an alist. The key is compared successively with the first element of each sub-list of the alist. The first sub-list whose first member "matches" the key is returned as the result of the function. If no match is found, false is returned.
(example '(assoc 'b e) '(b 2))
The difference between assoc, assq and assv lies in the equality function used to test for matching. assoc uses the equal function, and provides a capability that is generally useful, but may be rather slow. assq and assv use eq? eqv? respectively.
(example '(assq 'a e) '(a 1))
(example '(assq 'b e) '(b 2))
(example '(assq 'd e) #f)
(example '(assq '(a) '(((a) 4) ((b) 5) ((c) 7) )) #f)
(example '(assoc (list 'a) '(((a)) ((b)) ((c)))) '((a)))
(example '(assq 5 '((2 3) (5 7) (11 13))) '(5 7)) ; ** see note below
(example '(assv 5 '((2 3) (5 7) (11 13))) '(5 7))
** Note - the Scheme standard does not specify what the result of using eq? to compare numbers is, so this example may produce different results with different implementations of Scheme.
We could define assoc as follows:
(define (assoc obj alist)
(cond
((null? alist) #f)
((equal? obj (caar alist)) (car alist))
(else (assoc obj (cdr alist)))
))
Typically we will use assoc to locate the sublist in which a value resides, and then take the appropriate action to find the value. For example, an interpreter for a programming language might say something like:
(if (symbol? expr) (lookup expr environment) expr)
where the lookup function is defined as:
(define (lookup var env) ;;; find the value of a variable
(let ((pair (assoc var env))) ;;; find where its value is held
(if pair ;;; does an entry exist for the variable?
(cadr pair) ;;; extract its value
(error "unbound variable" var)
) ) )