Course 287: Lecture 9 Deep Lists and Deep Recursion

Deep Lists can be thought of as Trees.
Using An Accumulator to Make Computation More Efficient.
Association lists provide finite mappings.

Functions Defined in this Lecture.

(occurs_atom? a l) finds if atom a occurs in list structure l
(count_occurrences a l) finds if atom a occurs in list structurel
(subst x y expr) replaces all occurrences of atom x by y in list structureexpr
(assoc x alist)finds entry for x in alist
(assq x alist)finds entry for x in alist, using eq?
(assv x alist)finds entry for x in alist
(lookup x alist)finds value for x in alist
(flatten tree) makes a list of all the objects stored in nodes of tree.
(reverse l) makes a list of the objects of l in reverse order

Deep Lists can be thought of as Trees.

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)

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.

occurs_atom? and count_occurrences

For example, we may write a function occurs_atom? to find out if a given atom (non-pair) occurs anywhere in a nested list:

(define (occurs_atom? x expr)
        ((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)
        ((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

The subst function.

If we are mechanising any aspect of mathematics we are going to want to implement the substitution operation, in which one sub-expression is systematically replaced by another sub-expression throughout an entire large expression. Typically, we may replace a variable by an expression. Here is a function which substitutes for variables.

(define (subst x y expr)
        (atom? expr)
        (if (eq? x expr) y expr)
        (cons (subst x y (car expr))
            (subst x y (cdr expr))

Using An Accumulator to Make Computation More Efficient.

The flatten function

Consider the function flatten which takes a tree and makes a (flat) list of all the "tips" of the tree.

(define (flatten tree)
       ((null? tree) '())
       ((atom? (car tree)) (cons (car tree) (flatten (cdr tree))))
          (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)
        ((null? tree) ans)
         ((atom? tree) (cons tree ans))
            (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))

(reverse l> makes a list of the elements of l in reverse order

The simple recursive definition of reverse is this:

(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)
        (reverse_1 (cdr l) (cons (car l) acc))

(reverse '(2 3 4))
(4 3 2)

Association lists provide finite mappings.

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)
        ((null? alist) #f)
        ((equal? obj (caar alist)) (car alist))
        (else (assoc obj (cdr alist)))

The lookup function

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)
           ) ) )