One of the pleasant features of programming is that it is often possible to write some procedure that you need by making small changes in an existing procedure -- either one that you yourself wrote, or one that you encountered in a procedure library or a textbook. An experienced programmer recognizes patterns -- structural similarities between algorithms -- that can be applied repeatedly, with variations.
For instance, the procedure for doubling each element on a given list of numbers and the procedure for listing the lengths of the strings on a given list have a common structure:
(define double-each-element
(lambda (ls)
(if (null? ls)
'()
(cons (* 2 (car ls)) (double-each-element (cdr ls))))))
(define length-of-each-element
(lambda (ls)
(if (null? ls)
'()
(cons (string-length (car ls)) (length-of-each-element (cdr ls))))))
Once one has seen and understood both of these, it is not much of a stretch to figure out how to define a procedure that takes reverses each list on a list of lists and returns a list of the reversed lists:
(define reverse-each-element
(lambda (ls)
(if (null? ls)
'()
(cons (reverse (car ls)) (reverse-each-element (cdr ls))))))
Similarly, we have seen a procedure for constructing a list containing a given number of repetitions of a given value:
(define replicate
(lambda (count value)
(if (zero? count)
'()
(cons value (replicate (- count 1) value)))))
and a similar procedure for constructing a string containing a given number of repetitions of a given string:
(define replicate-string
(lambda (count template)
(if (zero? count)
""
(string-append template (replicate-string (- count 1) template)))))
so it is not difficult to come up with the somewhat similar procedure for
``wrapping'' a given number of pairs of parentheses around a given value,
by repeated applications of list:
(define wrap-up
(lambda (count value)
(if (zero? count)
value
(list (wrap-up (- count 1) value)))))
> (wrap-up 5 'foo)
(((((foo)))))
The common pattern in this case is that the numerical parameter counts down
the number of times that some simple operation remains to be applied; in
the if-expression, the test checks whether this countdown has
reached zero, the consequent supplies the base-case value of the procedure,
and the alternative applies the simple operation once to the slightly
simpler result returned by the recursive call.
A third pattern is list recursion with a conditional test on each element,
as in filter-out-negatives --
(define filter-out-negatives
(lambda (ls)
(cond ((null? ls) '())
((negative? (car ls)) (filter-out-negatives (cdr ls)))
(else (cons (car ls) (filter-out-negatives (cdr ls)))))))
-- or as in the following definition of
product-of-odd-elements, which takes any list of integers and
determines the product of just those elements that are odd:
(define product-of-odd-elements
(lambda (ls)
(cond ((null? ls) 1)
((odd? (car ls)) (* (car ls) (product-of-odd-elements (cdr ls))))
(else (product-of-odd-elements (cdr ls))))))
A slight variation of this pattern is found in the definition of the
following definition of the all-symbols? predicate, which
takes any list and determines whether it contains nothing but symbols as
elements:
(define all-symbols?
(lambda (ls)
(cond ((null? ls) #t) ; Vacuously, an empty list has nothing but symbols.
((symbol? (car ls)) (all-symbols? (cdr ls)))
(else #f))))
In this case, the else-clause need not contain a recursive
call, because we know what the result must be without even looking at the
subsequent elements of ls.
Look back over the lab exercises from the first three weeks of the course. Are there other examples of the patterns identified here? What other recurrent patterns are worth remembering?
Add a precondition test to each of the examples given above.
Catch up on any of the exercises from previous labs that you haven't previously done. Try some of the exercises on pages 81-84 of the textbook.
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/courses/scheme/patterns.html
created February 8, 1998
last revised February 8, 1998