Many of these functions were tested with a different version of Scheme (one that included nil). I've made updates, but I won't guarantee that everything works correctly.
As you may have noted from your career as a student of computer science, it is essential to develop good and comprehensive testing routines for your attempted implementations of algorithms, using both black-box (we know what it's supposed to do, but not how it does it) and white-box (we also know how it does it, and want to make sure we exercise the various parts) techniques. In the case of sorting routines, it is particularly important to develop reasonably comprehensive testing routines. Here is a reasonable black-box test routine (although perhaps too slow), written in a generic imperative language
for each list size from 0 to some reasonable size (e.g., 8) for each kind of list of that size generate a sample list of that kind and size for each permutation of that list make sure the sorting mechanism works
What ``kinds'' of lists do we want? At least four kinds: one kind of list has no duplicates. Since we're building every permutation, it seems reasonable to simply generate a sequence of numbers (possibly starting with a negative number and ending with a positive one so that we also test positive/negative problems). A second kind of list has all the same value to ensure that numbers aren't lost. A final kind of list is of a mixed form: some duplicates, some differences.
Your goal will be to translate these ideas into a comprehensive sort
testing predicate, (sorts? function) that takes a sorting
function as a parameter and returns true (
#t) if the
parameter seems to be a correct sorting routine (or at least passes a
relatively comprehensive suite of tests and false (
it fails some test.
Since there are lots of permutations of typical lists, you need only use 4 or 5 as the ``reasonable size'' in this case. In practice, we'd use much larger sizes.
A.1. Generating Sequences
Write a Scheme function
(nints n start) that generates a
n successive integers starting with
(nints 4 -2) would produce the list
(-2 -1 0 1).
;;; Compute the first n integers beginning with start. ;;; Precondition: n >= 0 ;;; Postcondition: returns (start start+1 start+2 ... start+n-1) ;;; Strategy: ;;; Base case: if n is 0, return the empty list ;;; Recursive case: compute the n-1 integers starting with start+1 ;;; (that is, (start+1 start+2 ... start+n-1)), and then prepend ;;; start. ;;; Note: ;;; I've used n <= 0 as the base case to handle obnoxious people ;;; who pass in a negative list length. (define (nints n start) (if (<= n 0) '() (cons start (nints (- n 1) (+ start 1)))))
A.2. Generating Copies
Write a Scheme function
(ncopies n val) that generates a
n copies of
val. For example,
(ncopies 4 3) would produce
(3 3 3 3).
;;; Generate a list of n copies of val ;;; precondition: n >= 0 ;;; postcondition: returns the list (val val val ... val) such that ;;; length of the list is n (define (ncopies n val) (if (<= n 0) '() (cons val (ncopies (- n 1) val))))
A.3. Generating Compound Sequences
Write a Scheme function that generates lists which have some of the criteria given above. That is, they should include both different numbers and copies of some number (as long as the list has at least three elements).
There are certainly a number of ways to answer this question (which
had an intentionally vague wording). My preference was for a function
that took the list size as a parameter. I've chosen one simple technique,
in which we do something like
nints, but alternating the
``what we add'' between 3 and 0. This way, we get lots of duplicates,
but also some spacing between the elements.
;;; Generate a list that includes both duplicates and different numbers. ;;; Make it an ordered list so that it's easy to check if we've sorted ;;; it correctly. ;;; Alternate adding 3 and 0 to the last element added. (define (dupe-and-diff n) (letrec ((helper (lambda (n val inc) (if (<= n 0) '() (cons val (helper (- n 1) (+ val inc) (if (= inc 0) 3 0))))))) (helper n 1 0)))
A.4. Generating Permutations
Write a recursive Scheme function,
(permutations lst), that
a list of all the permutations of
lst. For example,
(permutations '(1 2 3)) should produce something like
( (1 2 3) (2 1 3) (2 3 1) (1 3 2) (3 1 2) (3 2 1) )
Chat with me if you need some ideas on how to do this recursively.
Since we're writing this recursively, we need to consider what we can recurse on. The most likely candidate is the list itself. So, to compute the permutations of a list, l, we
The recursive part is easy. But how do we reinsert the car into a single
permutation? We need to make a list of lists, with the car inserted at
each place. We'll call that function
When writing that function, it is likely that we'll benefit from
insert-at, which inserts an element at a particular place.
We'll also need to write that function.
How do we reinsert the car into every permutation? Using
map. But that gives us a list of lists of lists.
We then need to concatenate those lists. We can just apply
append to that list.
Are there more elegant ways to do this? Certainly. This is just one strategy.
;;; Compute all permutations of a list. ;;; Design: ;;; If the list is empty, then the list of the empty list gives all of its ;;; permutations (the same is true for the single-element list, but ;;; we'll handle it in the recursive case). ;;; Otherwise, compute all the permutations of the cdr, insert the ;;; car "everywhere" in each permutation, and join the results ;;; together. (define (permutations lst) (if (= (length lst) 0) (list '()) (let ((subperms (permutations (cdr lst))) (first (car lst))) (apply append (map (lambda (sub) (insert-everywhere first sub)) subperms))))))
Note that this function actually generates a superset of the
permutations in that the same list can appear multiple times within the
list of permutations. For example, if we asked for the permutations
(1 1 2), we'd get
( (1 1 2) (1 1 2) (1 2 1) (1 2 1) (2 1 1) (2 1 1) )
We can solve this by removing duplicates from the resultant list. How to do so is left as an exercise for the reader.
Now, how can we write
insert-everywhere? One way is
recursively, using a helper function that counts the positions at
which we've insert the element. I suggested such a solution to
almost everyone who asked for help. However, it's also possible
to do this using
map. Basically, we make a list of
the positions at which to insert an element, and then map an
appropriate call to
insert-at onto that list.
;;; Given an element and a list, create a list of lists such that ;;; each element of the list-of-lists consists of the original ;;; list with the element inserted at some position. (define (insert-everywhere elt lst) (map (lambda (pos) (insert-at pos elt lst)) (nints (+ 1 (length lst)) 1)))
How can we write
insert-at? This seems easiest to
write recursively. We'll recurse on the position to insert and
the list itself. That is, to insert at the nth position in
list l, we can insert at the n-1st position in the cdr of l (and
then rebuild, due to the way Scheme handles lists).
;;; Given a position, an element, and a list, insert the element ;;; at the appropriate position of the list. For example, ;;; Precondition: 1 <= pos <= (length lst) + 1 ;;; (insert-at 1 'a '(b c d)) => (a b c d) ;;; (insert-at 2 'a '(b c d)) => (b a c d) ;;; (insert-at 3 'a '(b c d)) => (b c a d) ;;; (insert-at 4 'a '(b c d)) => (b c d a) ;;; (insert-at 4 'a '(b c d)) => BOOM (define (insert-at pos elt lst) (if (= pos 1) (cons elt lst) (cons (car lst) (insert-at (- pos 1) elt (cdr lst)))))
A.5. Developing a Testing Predicate
Using the pieces developed above, develop a predicate,
(sorts? fun) that returns true if
appears to sort lists and false otherwise.
As a strategy, we'll develop a function,
failed-permutations, that, given a sorting function and a
sorted list, lists all the permutations of that list that the sorting
function fails to sort. As a helper function for that, we'll develop a
sorts-perm? that checks whether a sorting
function sorts one permutation of a sorted list.
Note that we derive great benefits from starting with a sorted list. In particular, to determine whether a sorted version of a permutation is a correct sorting, we only need compare it to the starting list (thereby ensuring not only that the list is in order, but that it contains the correct elements).
;;; Given a sorting function, f, a sorted list, l, and a permutation of ;;; that list, p, determine if f correctly sorts l. (define (sorts-perm? f l p) (equal? l (f p)))
And a quick test (not a great one, but I'm pretty sure that it's correct given it's short length).
> (sorts-perm? id '(1 2 3) '(1 2 3)) #t > (sorts-perm? id '(1 2 3) '(2 1 3)) #f
Now, we can move on to checking whether the sorting function works with all permutations. Note that it might be easier to return true or false, but I decided I wanted a list of when sorting failed.
;;; Given a sorting function, sortfun, and a sorted list, l, determines ;;; all permutations of l that the sorting function fails to sort. ;;; If the sorts every function successfully, return nil. (define (failed-permutations sortfun l) (select (lambda (perm) (not (sorts-perm? sortfun l perm))) (permutations l)))
We can easily turn this into a predicate with
;;; Given a sorting function, sortfun, and a sorted list, l, determine ;;; if sortfun correctly sorts all permutations of l. (define (sorts-all-permutations? sortfun l) (null? (failed-permutations sortfun l)))
;;; Given a sorting function, sort, and a number, n, determine ;;; whether the sorting function seems to sort all lists of size ;;; up to n. (define (sorts-up-to? sort n) (if (<= n 0) (equal? '() (sort '())) (and ;;; Try lists of positive numbers (sorts-all-permutations? sort (nints n 1)) ;;; Try lists of negative and positive numbers (sorts-all-permutations? sort (nints n (- 0 (round (/ n 2))))) ;;; Try lists of identical numbers (sorts-all-permutations? sort (ncopies n 0)) (sorts-all-permutations? sort (ncopies n 1)) ;;; Try the weirdo lists that are sorted differently ;;; if textual sorting is used (sorts-all-permutations? sort (weird-list n)) ;;; Try our standard list with both duplicate and ;;; different elements. (sorts-all-permutations? sort (dupe-and-diff n)) ;;; And recurse (sorts-up-to? sort (- n 1)))))
If we so desire, we could also turn this into something that lists all the lists that we fail to sort. Here's a start. (Note that this code is incomplete.)
;;; Build a list of lists of up to size n that our function fails to sort. ;;; Certainly not guaranteed to provide all such lists, if there are ;;; some that the function fails to sort, is likely to produce at ;;; least one such list. (define (fails-to-sort sortfun n) (if (= n 0) (failed-permutations sortfun '()) (append (failed-permutations sortfun (nints n 1)) (failed-permutations sortfun (ncopies n 1)) (fails-to-sort sortfun (- n 1)))))
sorts? function can be written using one of these.
If we want to test lists up to size 7, we will use
;;; See if our sorting function seems to sort lists up to size 7. (define (sorts? sortfun) (sorts-up-to? sortfun 7))
A.6. Developing a Quicksort Function
Write a function,
(quicksort lst), that computes a sorted
lst using the quicksort algorithm. Run your
testing predicate on your sorting routine and report the results.
;;; Sort a list of numbers using the legendary Quicksort procedure. ;;; We pick a pivot, split the list into two parts: smaller than the ;;; pivot and greater than the pivot. We then sort the smaller and lesser ;;; sublists and join the results together. ;;; ;;; Note that we need to make sure that each sublist has at least one ;;; fewer element than the current list. Otherwise, this procedure ;;; may never terminate. (define (quicksort lst) (if (null? lst) () (let ((pivot (car lst))) (append (quicksort (select (lambda (x) (< x pivot)) (cdr lst))) (list pivot) (quicksort (select (lambda (x) (> x pivot)) (cdr lst)))))))
Here's another way to look at pivoting. We'll make a list of three lists: those less than the pivot, those equal to the pivot, and those greater than the pivot. We'll do so using a nice tail-recursive procedure.
;;; Given a list of numbers and a ``pivot value'' segment the list ;;; into three parts: those less than the pivot, those equal to ;;; the pivot, and those greater than the pivot. Returns those ;;; three lists. (define (segment pivot lst) (segmenthelper pivot lst '() '() '())) ;;; Given a pivot, list, and three helper lists, divide the first ;;; list into three parts: those less than the pivot, those equal ;;; to the pivot, and those greater than the pivot. Each list is ;;; joined to the corresponding helper list. (define (segmenthelper pivot lst subSmaller subEqual subGreater) (cond ;;; Base case. The list is empty, return the conjunction ;;; of the three sublists. ((null? lst) (list subSmaller subEqual subGreater)) ;;; Smaller case. ((< (car lst) pivot) (segmenthelper pivot (cdr lst) (cons (car lst) subSmaller) subEqual subGreater)) ;;; Bigger case ((> (car lst) pivot) (segmenthelper pivot (cdr lst) subSmaller subEqual (cons (car lst) subGreater))) ;; Default (else (segmenthelper pivot (cdr lst) subSmaller (cons (car lst) subEqual) subEqual))))
We can then use this in defining
quicksort as follows.
;;; Sort a list of numbers using the legendary quick sort procedure. ;;; We pick a pivot, split the list into three parts: smaller than the ;;; pivot, equal to the pivot, and greater than the pivot. We then ;;; recursively sort the smaller and lesser sublists and join the results ;;; together. (define (nquicksort lst) (if (null? lst) '() (let ((triplet (segment (car lst) lst))) (let ((smaller (car triplet)) (middle (cadr triplet)) (larger (caddr triplet))) (append (nquicksort smaller) middle (nquicksort larger))))))
We can now run our testing predicate. Note that using length-8 lists took me less than a minute.
> (sorts-up-to? quicksort 8) #t
Here's a variant that may forget some elements. (And yes, it uses
quicksort for the recursive calls.)
(define (qsort lst) (if (null? lst) '() (let ((pivot (car lst))) (append (quicksort (select (lambda (x) (< x pivot)) lst)) (list pivot) (quicksort (select (lambda (x) (> x pivot)) lst))))))
Here's some testing. Note that using a testing style I've seen many students employ (``Oh boy, it seems to sort a list of random numbers''), this seems to work fine. However, if we do structured testing of the sorting method, it becomes clear that there are some simple length-three lists that it fails to sort.
> (sorts-all-permutations? qsort (nints -3 7)) #t > (qsort (randlist 20)) (-931 -898 -833 -801 -692 -690 -489 -361 -352 -215 -212 -89 55 318 521 647 723 756 806 902) > (sorts-up-to? qsort 3) #f
A.7. Developing a Merge Sort Function
Write a function,
(mergesort lst), that computes a sorted
lst using the mergesort algorithm. Run your
testing predicate on your sorting routine and report the results.
;;; Sort a list of numbers using the legendary merge sort procedure. ;;; In the recursive version of this procedure, we split the list in ;;; half, sort the two halves, and then merge the results. I've ;;; decided that it's okay to rearrange the list when splitting. (define (mergesort lst) ; First base case: empty list (if (null? lst) '() ; Second base case: single element list (if (null? (cdr lst)) lst ; Recursive case: split, sort the two halves, then merge (let ((halves (split lst))) (merge (mergesort (car halves)) (mergesort (cdr halves)))))))
For this to work, we need both
split breaks a list into a dotted pair of more-or-less equal
size lists by stepping through the list and prepending to one of two
lists. It is probably not the most elegant way to do things, but hey,
;;; Split a list into two lists of approximately the same size. ;;; Postcondition: each element in the original list appears exactly ;;; once in one of the two lists ;;; Postcondition: the two lists differ in length by at most 1. ;;; Returns: a list whose car is the first list and ;;; whose cdr is the second list. ;;; Note: May rearrange the elements. (define (split lst) (letrec ((splithelper (lambda (lst first second add-to-first) (if (null? lst) (cons first second) (if add-to-first (splithelper (cdr lst) (cons (car lst) first) second #f) (splithelper (cdr lst) first (cons (car lst) second) #t) ) ; if add-to-first ) ; if the lst is null ))) ;;; variables in the letrec (splithelper lst '() '() #t) ) ; letrec ) ; define split
Now we're ready to merge.
;;; Merge two sorted lists into a sorted list (define (merge first second) (if (null? first) second (if (null? second) first (if (< (car first) (car second)) (cons (car first) (merge (cdr first) second)) (cons (car second) (merge first (cdr second)))))))
> (sorts-up-to? mergesort 8) #t
Just for the fun of it, I implemented a non-working sorting procedure very similar to merge sort. Here it is.
;;; Sort a list using the legendary mrgsort procedure that splits ;;; the list in two, sorts the first half, and merges them together. (define (mrgsort lst) (if (null? lst) '() (if (null? (cdr lst)) lst (let ((halves (split lst))) (merge (mrgsort (car halves)) (cdr halves))))))
Here's some manual testing and then some automated testing.
> (mrgsort '(1 2 3)) (1 2 3) > (mrgsort '(3 2 1)) (1 2 3) > (mrgsort '(2 1 3)) (1 2 3) > (mrgsort '(1 1 1)) (1 1 1) > (sorts-up-to? mrgsort 0) #t > (sorts-up-to? mrgsort 1) #t > (sorts-up-to? mrgsort 2) #t > (sorts-up-to? mrgsort 3) #t > (sorts-up-to? mrgsort 4) #f > (fails-to-sort mrgsort 4) ((1 2 3 4) (2 1 3 4) (2 3 1 4) (1 3 2 4) (3 1 2 4) (3 2 1 4) (3 1 4 2) ...
Again, note that simple manual testing (and even some simple automated testing) doesn't reveal the errors in the method. However, testing on larger lists reveals that there are errors in the method.
A.8. Developing an Insertion Sort Function
Write a function,
(insertion-sort lst), that computes a sorted
lst using the insertion sort algorithm. Run your
testing predicate on your sorting routine and report the results.
;;; Sort a list by using the insertion sort procedure. We sort the ;;; cdr of the list and then insert the element in the appropriate ;;; place in that list. (define (insertion-sort lst) ; Base case: empty list (if (null? lst) lst ; Recursive case: sort the cdr and insert (insert (car lst) (insertion-sort (cdr lst)))))
Now we just have to write
;;; Insert an element into the appropriate place in a sorted list. (define (insert elt lst) ; Base case one, empty list: create a new list containing the element (if (null? lst) (list elt) ; Base case two, element falls before the first element in the list: ; cons 'em together (if (< elt (car lst)) (cons elt lst) ; Recursive case: insert into remainder of list (cons (car lst) (insert elt (cdr lst))))))
And we're ready to test.
> (sorts-up-to? insertion-sort 8) #t
Wasn't that thrilling?
As we've seen, Scheme generally performs a form of eager evaluation: before calling a function on arguments, it evaluates the arguments. Some time ago, a number of computer science researchers suggested that one use lazy evaluation, in which one delays evaluation of an expression as long as possible.
Scheme provides a few built-in functions to support this type of evaluation. However, it is also possible to support delayed evaluation using lambda expressions. Consider the lambda expression
(lambda () (+ a (* b c)))
This indicates "when this function is applied" (to nothing), multiply b and c and then add a". To apply this function, we simply compute an expression with it. For example,
> (define a 2) > (define b 3) > (define c 4) > (define fun (lambda () (+ a (* b c)))) > fun #<procedure fun> > (fun) 14 > (define a 100) > (fun) 112 > (define (foo a) (+ a (fun))) > (foo 1) 113
Let's consider how we might use this in building lists. Suppose we
wanted to build a list of four items and only used the forth. It
would obviously be a waste of computation power to compute all four
items in advance. Hence, we might encapsulate each in a lambda
expression and then only extract them when necessary. In fact, if
we were to take this idea to extremes, we might not even want to build
anything but the first
cons cell (leaving the construction
of the remaining ones to "on demand").
Why might this be useful? Well, it provides a different form of
program modularity. Consider the functions
which lists the first
n integers and
(nprimes n) which lists the first
Good program design suggests that we should extract out any common
features of these two functions. What is common? Getting the first
n elements in a sequence. We could define then define
(firstn n lis) as
(define (firstn n lis) (if (= n 0) '() (cons (car lis) (firstn (- n 1) (cdr lis)))))
Unfortunately, if we choose to do this, we need a way to build lists of unknown length. In effect, we need to delay construction of the list until the parts of the list are needed (or demanded). We'll call lists with encapsulated cars and cdrs encapsulated lists.
Write a function,
(demand encapsulated), that extracts
an encapsulated value from a lambda abstraction. For example,
(demand (lambda () (+ 2 3)))
should return 5.
;;; Demand the value associated with an encapsulated value by applying ;;; the function. (define (demand encapsulated) (encapsulated))
While this works fine for something like
(demand (lambda () 2))
it doesn't work so well for
as we can see in
> (demand (lambda () 5)) 5 > (demand 5) Error: attempt to apply non-procedure 5.
We can make our solution a little bit better by using the
procedure? predicate to check whether the
argument is a procedure. If so, we apply it. If not, we use
the current value. This won't work if the argument is a
non-nullary procedure, but that's the best we can do.
;;; Demand an encapsulated value, making sure that it seems to ;;; be encapsulated. (define (demand encapsulated) (if (procedure? encapsulated) (encapsulated) encapsulated))
This works with both encapsulated and nonencapsulated values. As we can
see from the third example below, the encapsulation really does work (as
"Hello" is not printed until we demand the value of
demand does fail on
functions which take arguments, as we see when we attempt to apply it to
> (demand 5) 5 > (demand (lambda () 5)) 5 > (define encap (lambda () (display "Hello") (newline) 5)) > (demand encap) Hello 5 > (demand car) Error: incorrect number of arguments to #<system procedure car>.
I did not expect your answers to be quite this sophisticated, and accepted the first definition.
B.2. List Unencapsulation
extract the actual
cdr of an encapulsated
list. For example,
> (define ls (cons (lambda () (display 'a) (newline) (+ 2 3)) (lambda () (cons (lambda () (display 'b) (newline) (* 3 4)) (lambda () '()))))) > (demandcdr (demandcdr ls)) () > (demandcar ls) a 5 > (demandcar (demandcdr ls)) b 12
;;; Compose two functions. (define (compose f g) (lambda (x) (f (g x)))) ;;; Demand the car of a list. (define demandcar (compose demand car)) ;;; Demand the cdr of a list. (define demandcdr (compose demand cdr))
B.3. Infinite Lists
Write a function,
(intsfrom n), that creates an encapsulated
list of all the integers from n to infinity.
;;; Create an encapsulated list of all the integers starting with n. (define (intsfrom n) (cons (lambda () n) (lambda () (intsfrom (+ n 1)))))
For fun, we might also create one that prints out each value ``seen'', which can be helpful when testing other lists.
(define (intsfromWithDisplay n) (cons (lambda () (begin (display n) (newline) n)) (lambda () (intsfromWithDisplay (+ n 1)))))
Create an appropriate variant of the
firstn function above
that works with encapsulated lists. Use it to test your
> (firstn 3 (intsfrom 5)) (5 6 7)
This is fairly straightforward.
;;; Grab the first n elements of an encapsulated list. (define (firstn n lst) (if (= n 0) '() (cons (demandcar lst) (firstn (- n 1) (demandcdr lst)))))
> (firstn 10 (intsfrom -4)) (-4 -3 -2 -1 0 1 2 3 4 5)
> (firstn 4 (intsfromWithDisplay 3)) 3 4 5 6 (3 4 5 6)
Because of the careful definition of
demand, we can
>firstn 5 '(1 2 3 4 5 6 7)) (1 2 3 4 5)
although this does suggest we should test for the empty list as an argument.
Write a function,
(filter pred encaplst) that, given a
predicate and an encapsulated list, creates a new encapsulated list
which contains only the members of the parameter meeting the predicate.
;;; Given a predicate and an encapsulated list, create a new ;;; encapsulated list containing only the elements that meet ;;; the predicate. (define (filter pred encap) ;;; If the list is empty, we're done. While we normally use ;;; only infinite lists, this may be helpful in a few cases. (if (null? encap) '() ;;; Get the first element of the list (let ((elem (demandcar encap))) ;;; If the predicate holds, build an encapsulated list (if (pred elem) (cons (lambda () elem) (lambda () (filter pred (demandcdr encap)))) ;;; Otherwise, recurse (filter pred (demandcdr encap))))))
Here is it in action.
> (firstn 3 (filter even? (intsfrom 3))) (4 6 8) > (firstn 3 (filter even? (intsfromWithDisplay 3))) 3 4 5 6 7 8 9 10 (4 6 8)
Observe that there seems to be a slight bug in that I'm looking at two elements that I shouldn't need to look at. Extra credit to the first person to find the bug.
B.6. More Filtering
filter, write a function
encaplst) that returns an encapsulated list containing only the
elements of the encapsulated list that are not multiples of n.
It is, of course, preferable to use the
from the previous step. We just need to choose the appropriate filter.
We want a function that, given a number,
x is a multiple of
n. The solution
is given below.
;;; Given an integer, n, and an encapsultated list of integers, ;;; create an encapsulated list containing only the elements of ;;; the original list that are not multiples of n. (define (filterOutMultiples n encap) (filter (lambda (x) (not (zero? (modulo x n)))) encap))
A few very simple tests.
> (firstn 4 (filteroutmultiples 3 (intsfrom 2))) (2 4 5 7) > (firstn 4 (filteroutmultiples 3 (intsfromWithDisplay 2))) 2 3 4 5 6 7 8 (2 4 5 7)
Using your other methods from this problem, write a function
primes that returns an encapsulated list of the
prime numbers. You will most likely want to use the Sieve of
Eratothenes: start with the numbers starting with 2. Repeatedly
take off the first number (that's a prime) and then filter out
multiples of that number.
We'll use the Sieve of Eratothenes. Given an ordered list of numbers, we find the ``primes'' in the list (those numbers that have no factors in the list) by repeatedly picking the next unfiltered element and then filtering out all multiples.
Since we're working with an infinite list, we, in effect, delay the filtering until it's absolutely necessary.
;;; Compute the standard list of primes as an encapsulated list. (define (primes) (primesHelper (intsfrom 2))) ;;; Given an ordered list of numbers (smallest first), find the ;;; primes in the list (those that have no factors in the list ;;; other than themselves). Uses the Sieve of Eratothenes. (define (primesHelper encap) (let ((prime (demandcar encap))) (cons (lambda () prime) (lambda () (primesHelper (filterOutMultiples prime (demandcdr encap)))))))
Now for some testing. First, the first twenty primes.
> (firstn 20 (primes)) (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)
Next, let's see how many times we look at each value in finding the first eight primes.
> (firstn 8 (primeshelper (intsFromWithDisplay 2))) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 (2 3 5 7 11 13 17 19)
Once again, it's gone a little too far. The offer of extra credit still stands.
As an intersting variant, let's find some of the ``primes'' in the list of even integers starting with 10.
> (firstN 8 (primesHelper (filter even? (intsfrom 10)))) (10 12 14 16 18 22 26 34)
Are there an infinite number of such ``primes''? A proof will get you some extra credit.
Disclaimer Often, these pages were created ``on the fly'' with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
This page may be found at http://www.math.grin.edu/~rebelsky/Courses/CS302/99S/Assignments/notes.02.html
Source text last modified Mon Feb 15 12:51:47 1999.
This page generated on Mon Feb 15 12:53:51 1999 by SiteWeaver. Validate this page's HTML.
Contact our webmaster at email@example.com