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.
|
created February 8, 1998 by
John David Stone
(stone@cs.grinnell.edu) last revised January 18, 2004 by Henry M. Walker at walker@cs.grinnell.edu. |
|
| For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |