4.1 heads and tails
Just for warming up, how do you find out whether a list x is the head of a list y, e.g.:
(headp ’#a ’#abc) => :t (headp ’#ab ’#abc) => :t (headp ’#abc ’#abc) => :t (headp ’#abcd ’#abc) => :f
A list x is the head of a list y, if their leading members up to the length of x are equal. Here is headp, which performs this test:
(define (headp x y)
(cond ((null y) (null x)) ((null x) :t)
(t (and (equal (car x) (car y)) (headp (cdr x) (cdr y))))))
According to headp, the empty list is the head of any list:
(headp () ’(foo bar baz)) => :t
Do you think this is the right way to handle the empty ‘‘head’’? If so, why? If not, why not?
How does the fact that the car part of a list is also called the ‘‘head’’ of a list contribute to this controversy? (Q1)
While we are at it: how would you check whether a list x is the tail of a list y. Do not ponder too long:
(require ’headp) (define (tailp x y)
(headp (reverse x) (reverse y)))
Why is that fact that
(tailp () ’#whatever) => :t
less controversial than the fact that
(headp () ’#whatever) => :t
Bonus exercise: find better names for headp and tailp.
4.2 find the n’th tail of a list
The nth function extracts the tail of a list that starts at the n’th element of that list. When the list is too short, it returns :f:
(nth ’#0 ’#abc) => ’#abc (nth ’#1 ’#abc) => ’#bc (nth ’#2 ’#abc) => ’#c (nth ’#5 ’#abc) => :f
Here is the code of nth:
(require ’~nmath) (define (nth n x) (cond ((zero n) x) ((null x) :f) (t (nth (- n ’#1) (cdr x)))))
When the argument n is the same as the the length of the list, nth returns an empty list. In partic-ular:
(nth ’#0 ()) => ()
When you swap the first two predicates of the cond of nth, it will return :f in the above case.
Which version do you consider more consistent? Why?
Do you think it is a good idea that the first list member has an offset of ’#0? Implement a version that starts numbering members at ’#1. What are the advantages and disadvantages of each version?
What will your version return when you pass an offset of zero to it?
4.3 count the atoms of a form
The count function counts the atoms of a form recursively:
(count ()) => ’#0 (count ’a) => ’#1
(count ’(a (b (c) d) e)) => ’#5
Here is the code of the count function:
(require ’~nmath) (define (count x) (cond ((null x) ’#0) ((atom x) ’#1)
(t (+ (count (car x)) (count (cdr x))))))
Strictly speaking, count counts only the symbols of a form, because () is also an atom. If count
returned ’#1 for (), though, its results would be counter-intuitive. Why? (Q2)
Count uses structural recursion. Do you think it can be written in a more efficient way?
This is an interesting question. Here is a version of count that uses tail calls exclusively:
(require ’~nmath) (define (count1 x) (letrec
((c (lambda (x r s) (cond ((null x)
(cond ((null s) r)
(t (c (car s) r (cdr s))))) ((atom x)
(c () (+ ’#1 r) s))
(t (c (car x) r (cons (cdr x) s))))))) (c x ’#0 ())))
It uses the internal function c that keeps its intermediate result (the atoms counted so far) in the extra parameter r. This technique was used earlier in this book to convert linear recursive programs to tail-recursive ones. Can this technique also be applied to turn structural recursion into tail recursion?
The c function uses an additional parameter s which stores the nodes to be visited in the future.
Whenever it finds a new pair, it saves its cdr part in s and then starts traversing the car part of that pair.
When count finds an instance of (), it removes the head of s and traverses the structure that was stored there. Of course, the name s should invoke the connotation of a ‘‘stack’’, and this is exactly what it is.
The only difference between the structural recursive version and the version using tail calls is that the version using tail calls stores activation records on an explicit stack rather than onzenlisp’s internal stack. The recursion itself cannot be removed. It is inherent in the problem.
This particular case illustrates nicely that attempting to remove recursion from solutions for inherently recursive problems often decreases readability while it barely increases efficiency.
4.4 flatten a tree
A tree is ‘‘flattened’’ by turning it into a flat list that contains the same member in the same order as the original tree:
(flatten ’((a) (b (c)) (d (e (f))))) => ’#abcdef
Here is the code of flatten1, which performs this operation --- although in a highly inefficient way:
(define (flatten1 x) (cond ((null x) x)
((atom x) (list x))
(t (append (flatten1 (car x)) (flatten1 (cdr x))))))
Can you name a few reasons why this implementation is far from efficient? (Yes, the obvious structural recursion is only one of them.)
Here is an improved version. It uses a clever trick to avoid structural recursion:
(define (flatten x) (letrec
((f (lambda (x r)
(cond ((null x) r)
((atom x) (cons x r)) (t (f (car x)
(f (cdr x) r))))))) (f x ())))
The function still applies itself twice, but the result of the first application is passed to the second one, which is a tail call, thereby turning structural recursion into linear recursion.
Can this version of flatten be transformed into a version using tail calls, like count? Would this transformation improve efficiency any further? (Q3)
4.5 partition a list
In the previous part the functions filter and remove were introduced [page 42]. One of the functions extracts members satisfying a predicate, the other one removes members satifying a predicate.
Can you write a program that combines the functions of filter and remove? Hint: the function should return two lists.
Here is the code of partition, which partitions a list into members satisfying and not satisfying a given predicate:
(define (partition p a) (letrec
((partition3
(lambda (a r+ r-) (cond ((null a)
(list r+ r-)) ((p (car a))
(partition3 (cdr a)
(cons (car a) r+) r-))
(t (partition3 (cdr a) r+
(cons (car a) r-))))))) (partition3 (reverse a) () ())))
The first member of its result is equal to the output of filter and its second member resembles the output of remove. Would it make sense to implement filter and remove on top of partition?
4.6 folding over multiple lists
The fold function (explained on page 45, code on page 271) folds lists to values:
(fold (lambda (x y) (list ’op x y)) ’0 ’(a b c))
=> ’(op (op (op 0 a) b) c)
The Revised6 Report on the Algorithmic Language Scheme (R6RS) --- although being highly controversial --- defines a few interesting functions. One is fold-left, which allows to fold over multiple lists:
(fold-left (lambda (x y z) (list ’op x y z))
’0
’(a b c)
’(d e f))
=> ’(op (op (op 0 a d) b e) c f)
All lists must have the same length. Can you implement fold-left? Hint: this function has some things in common with map [page 44] and fold. In particular, the following functions are helpful when implementing it:
(define (car-of a) (map car a)) (define (cdr-of a) (map cdr a))
In fact, fold-left is exactly like fold, but uses the techniques of map to handle variadic ar-guments:
(define (fold-left f b . a*) (letrec
((fold
(lambda (a* r)
(cond ((null (car a*)) r) (t (fold (cdr-of a*)
(apply f r (car-of a*)))))))) (cond ((null a*) (bottom ’too-few-arguments)) (t (fold a* b)))))
The R6RS also defines a variant of fold-r [page 271] that can handle multiple lists. Unsurpris-ingly it is called fold-right. It folds over multiple lists as follows:
(fold-right (lambda (x y z) (list ’op x y z))
’0
’(a b c)
’(d e f))
=> ’(op a d (op b e (op c f 0)))
Fold-right is a bit trickier to write than fold-left. Do you want to give it a try before looking at the
following code?
(define (fold-right f b . a*) (letrec
((foldr
(lambda (a* r)
(cond ((null (car a*)) r) (t (foldr (cdr-of a*)
(apply f (append (car-of a*) (list r))))))))) (cond ((null a*) (bottom ’too-few-arguments))
(t (foldr (map reverse a*) b)))))
Fold-right uses append in its general case. Is this a problem? If so, what can you do about it? If not, why not? (Q4)
4.7 substitute variables
The substitute function replaces variables in forms with values stored in a given environment:
(define env ’((x . foo) (y . bar)))
(substitute ’(cons x y) env) => ’(cons foo bar)
Substitute is rather straight forward to implement. You may try it before reading ahead. Hint: its code is similar to replace [page 30].
(define (substitute x env) (letrec
((value-of (lambda (x)
(let ((v (assq x env))) (cond (v (cdr v)) (t x))))) (subst
(lambda (x)
(cond ((null x) ())
((atom x) (value-of x)) (t (cons (subst (car x))
(subst (cdr x)))))))) (subst x)))
What happens if you remove the clause ((null x) ()) from the cond of subst?
Why can substitute not be used to substitute variables with values in the bodies of lambda forms?
Hint: this is related to beta reduction [page 59]. (Q5)