With the help of recursion, we can perform another common operation on lists: filtering. One might, for example, write a procedure that takes any list of real numbers and returns a similar list, but with all the negative numbers removed; such a procedure would ``filter out'' the negative numbers. Here's how it would look:
(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)))))))
In English: If the list ls is empty, return the empty
list. Otherwise, if the first element on the list is negative, discard it
and proceed to apply the filter to the rest of the list. But if the first
element on the list is zero or positive, add it to the front of the list
obtained by applying the filter to the rest of the list.
Start Scheme and enter the definition of filter-out-negatives.
Confirm that it correctly filters out negatives from any list of numbers.
What happens if all of the elements of the list are negative? What happens
if ls contains symbols instead of numbers?
Write and test a Scheme procedure filter-outliers that takes a
list of real numbers and filters out those that are not in the range from 0
to 100.
> (filter-outliers '(93 86 92 -3 100 81 77 84)) (93 86 92 100 81 77 84) > (filter-outliers '(-50 0 50 100 150)) (0 50 100) > (filter-outliers '(1000000)) () > (filter-outliers '()) ()
Recursion can be used simply to run through a series of numerical values, so that each of those values can be operated on by a fixed procedure. For instance, the following procedure uses square from the user-defined procedures lab to compute the sum of the squares of the positive integers from 1 to n, for any non-negative integer n:
(define sum-of-squares
(lambda (n)
(if (zero? n)
0
(+ (square n) (sum-of-squares (- n 1))))))
Adapt this procedure so that squares of odd numbers are excluded from the sum.
Note: Exercise 3 requires the identificatin of odd numbers, and many future tasks will require a variety of other tests and operations. Thus, if you have not already done so, you may want to review some procedures built into Scheme, as defined by the Revised (5) Report on the Algorithmic Language Scheme. In particular, you may want to check that this reference is included among your bookmarks in your World-Wide Web browser. Then, you may want to move to that web page, and follow the table of contents to the bottom to the Alphabetic index of definitions of concepts, keywords, and procedures. Browsing through the list of procedures, for example, you may find some entries related to odd or even numbers. Clicking on such entries might make interesting reading.
In other cases, the numerical parameter is simply a counter, tallying the
number of repetitions of some operation that may not involve numbers at
all. For instance, we might want a procedure dupl that takes
two arguments, a string str and a non-negative integer
factor, and returns a string consisting of factor
successive copies of str:
> (dupl "alf" 4) "alfalfalfalf" > (dupl "What? " 5) "What? What? What? What? What? " > (dupl "*-" 10) "*-*-*-*-*-*-*-*-*-*-" > (dupl "" 153) "" > (dupl "invisible" 0) ""
Here's how we'd write it:
(define dupl
(lambda (str factor)
(if (zero? factor)
""
(string-append str (dupl str (- factor 1))))))
The string-append operation itself deals only with strings,
not with numbers; the role of factor is simply to count off
the number of repetitions remaining and to cut off the recursion once this
number decreases to zero.
Write and test a procedure dupl-to-fit, similar to
dupl, except that the second parameter indicates the maximum
length permitted for the result string -- str should be
duplicated as many times as possible without exceeding this maximum length.
(In other words, the recursion should terminate as soon as the amount of
``free space'' left for the result string is less than the length of
str.)
> (dupl-to-fit "alf" 11) "alfalfalf" > (dupl-to-fit "alf" 12) "alfalfalfalf" > (dupl-to-fit "*" 7) "*******" > (dupl-to-fit "*-" 7) "*-*-*-" > (dupl-to-fit "invisible" 3) ""
Be careful not to use the null string as the first argument to this procedure!
One thing that Scheme programmers often want to do is test whether a given
value occurs as one of the elements of a given list. Using recursion, we
can easily define a predicate occurs-as-element? that performs
this test:
(define occurs-as-element?
(lambda (value ls)
(and (not (null? ls))
(or (equal? value (car ls))
(occurs-as-element? value (cdr ls))))))
In English: value occurs as an element of ls if
(1) ls is not the empty list (since the empty list has no
elements) and (2) either value is equal to the first element
of ls or value is an element of the part of the
ls that follows the first element.
Occurs-as-element? really is a predicate, since it always
returns #t or #f specifically.
Because tests of this sort are so frequent, Scheme provides a built-in
procedure member that also performs it. However, the
designers of Scheme felt that it would often be useful for
member to return more than a simple yes-or-no answer: When the
answer is ``yes,'' member actually returns the part of
ls that begins with the value sought:
> (member 'gamma '(alpha beta gamma delta epsilon)) (gamma delta epsilon) > (member 1 '(1 2 3 4)) (1 2 3 4) > (member "not" (list "and" "to" "by" "for")) #f
It is as if member were defined as follows:
(define member
(lambda (value ls)
(cond ((null? ls) #f)
((equal? value (car ls)) ls)
(else (member value (cdr ls))))))
A list A is a subset of a list B if each of the
elements of A occurs as an element of B. (An empty list
counts as a subset of any list -- ``vacuously,'' as mathematicians
say.) Define a Scheme procedure, using either member or
occurs-as-element?, that takes two lists as arguments and
returns #t if the first is a subset of the second,
#f if it is not.
Scheme also provides the procedures memq and
memv, which are exactly like member except that
they compare value to each of the elements of ls
by invoking eq? or eqv? rather than
equal?. They are therefore slightly faster than
member, but in general they are used only when it is known in
advance that none of the elements of ls is a list.
For more practice with recursion, look over exercises 2.12 through 2.21 (pages 54-57 in the textbook), 3.1 through 3.4 (pages 81 and 82), and 3.6 through 3.13 (pages 82-84) and try any that seem interesting. (Exercise 3.5 is not recommended at this point. It can be done, but the procedure it asks you to define can be written much more easily and naturally using other Scheme constructions that will be introduced in a couple of weeks.)
This document is available on the World Wide Web as
http://www.math.grin.edu/~walker/courses/151.fa98/lab-more-recursion.html
created February 5, 1997 by John David Stone
last revised September 14, 1998 by Henry M. Walker