We have seen how to support the illusion of manipulating streams as complete entities even though, in actuality, we compute only as much of the stream as we need to access. We can exploit this technique to represent sequences efficiently as streams, even if the sequences are very long. What is more striking, we can use streams to represent sequences that are infinitely long. For instance, consider the following definition of the stream of positive integers:
(define (integers-starting-from n) (cons-stream n (integers-starting-from (+ n 1))))This makes sense because integers will be a pair whose car is 1 and whose cdr is a promise to produce the integers beginning with 2. This is an infinitely long stream, but in any given time we can examine only a finite portion of it. Thus, our programs will never know that the entire infinite stream is not there.(define integers (integers-starting-from 1))
Using integers we can define other infinite streams, such as the stream of integers that are not divisible by 7:
(define (divisible? x y) (= (remainder x y) 0))
(define no-sevens
(stream-filter (lambda (x) (not (divisible? x 7)))
integers))
Then we can find integers not divisible by 7 simply by accessing
elements of this stream:
(stream-ref no-sevens 100) 117
In analogy with integers, we can define the infinite stream of Fibonacci numbers:
(define (fibgen a b) (cons-stream a (fibgen b (+ a b)))) (define fibs (fibgen 0 1))Fibs is a pair whose car is 0 and whose cdr is a promise to evaluate (fibgen 1 1). When we evaluate this delayed (fibgen 1 1), it will produce a pair whose car is 1 and whose cdr is a promise to evaluate (fibgen 1 2), and so on.
For a look at a more exciting infinite stream, we can generalize the
no-sevens example to construct the infinite stream of prime
numbers, using a method known as the
sieve of
Eratosthenes.
We
start with the integers beginning with 2, which is the first prime.
To get the rest of the primes, we start by filtering the multiples of
2 from the rest of the integers. This leaves a stream beginning with
3, which is the next prime. Now we filter the multiples of 3 from the
rest of this stream. This leaves a stream beginning with 5, which is
the next prime, and so on. In other words, we construct the primes by
a sieving process, described as follows: To sieve a stream S,
form a stream whose first element is the first element of S and
the rest of which is obtained by filtering all multiples of the first element
of S out of the rest of S and sieving the result. This
process is readily described in terms of stream operations:
(define (sieve stream)
(cons-stream
(stream-car stream)
(sieve (stream-filter
(lambda (x)
(not (divisible? x (stream-car stream))))
(stream-cdr stream)))))
(define primes (sieve (integers-starting-from 2)))
Now to find a particular prime we need only ask for it:
(stream-ref primes 50) 233
It is interesting to contemplate the signal-processing system set up
by sieve, shown in the
``Henderson diagram'' in
figure
.
The input stream feeds into an
``unconser'' that separates the first element of the stream from the
rest of the stream.
The first element is used to construct a divisibility filter, through
which the rest is passed, and the output of the filter is fed to
another sieve box. Then the original first element is consed onto the
output of the internal sieve to form the output stream. Thus, not
only is the stream infinite, but the signal processor is also
infinite, because the sieve contains a sieve within it.
Defining streams implicitly
The integers and fibs streams above were defined by specifying ``generating'' procedures that explicitly compute the stream elements one by one. An alternative way to specify streams is to take advantage of delayed evaluation to define streams implicitly. For example, the following expression defines the stream ones to be an infinite stream of ones:
(define ones (cons-stream 1 ones))This works much like the definition of a recursive procedure: ones is a pair whose car is 1 and whose cdr is a promise to evaluate ones. Evaluating the cdr gives us again a 1 and a promise to evaluate ones, and so on.
We can do more interesting things by manipulating streams with
operations such as add-streams, which produces the elementwise
sum of two given streams:
(define (add-streams s1 s2) (stream-map + s1 s2))Now we can define the integers as follows:
(define integers (cons-stream 1 (add-streams ones integers)))This defines integers to be a stream whose first element is 1 and the rest of which is the sum of ones and integers. Thus, the second element of integers is 1 plus the first element of integers, or 2; the third element of integers is 1 plus the second element of integers, or 3; and so on. This definition works because, at any point, enough of the integers stream has been generated so that we can feed it back into the definition to produce the next integer.
We can define the Fibonacci numbers in the same style:
(define fibs
(cons-stream 0
(cons-stream 1
(add-streams (stream-cdr fibs)
fibs))))
This definition says that fibs is a stream beginning with 0 and
1, such that the rest of the stream can be generated by adding fibs
to itself shifted by one place:
| 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | ... = (stream-cdr fibs) | ||
| 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | ... = fibs | ||
| 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | ... = fibs |
Scale-stream is another useful procedure in formulating such stream definitions. This multiplies each item in a stream by a given constant:
(define (scale-stream stream factor) (stream-map (lambda (x) (* x factor)) stream))For example,
(define double (cons-stream 1 (scale-stream double 2)))produces the stream of powers of 2: 1, 2, 4, 8, 16, 32, ....
An alternate definition of the stream of primes can be given by starting with the integers and filtering them by testing for primality. We will need the first prime, 2, to get started:
(define primes (cons-stream 2 (stream-filter prime? (integers-starting-from 3))))This definition is not so straightforward as it appears, because we will test whether a number n is prime by checking whether n is divisible by a prime (not by just any integer) less than or equal to
(define (prime? n)
(define (iter ps)
(cond ((> (square (stream-car ps)) n) true)
((divisible? n (stream-car ps)) false)
(else (iter (stream-cdr ps)))))
(iter primes))
This is a recursive definition, since primes is defined in terms
of the prime? predicate, which itself uses the primes
stream. The reason this procedure works is that, at any point, enough
of the primes stream has been generated to test the primality of
the numbers we need to check next. That is, for every n we test for
primality, either n is not prime (in which case there is a prime
already generated that divides it) or n is prime (in which case
there is a prime already generated--i.e., a prime less than
n--that is greater than
Exercise. Without running the program, describe the elements of the stream defined by
(define s (cons-stream 1 (add-streams s s)))
Exercise. Define a procedure mul-streams, analogous to add-streams, that produces the elementwise product of its two input streams. Use this together with the stream of integers to complete the following definition of the stream whose nth element (counting from 0) is n+1 factorial:
(define factorials (cons-stream 1 (mul-streams ?? ??)))
Exercise. Define a procedure partial-sums that takes as argument a stream S and returns the stream whose elements are S0, S0+S1, S0+S1+S2, .... For example, (partial-sums integers) should be the stream 1, 3, 6, 10, 15, ....
Exercise. A famous problem, first raised by R. Hamming, is to enumerate, in ascending order with no repetitions, all positive integers with no prime factors other than 2, 3, or 5. One obvious way to do this is to simply test each integer in turn to see whether it has any factors other than 2, 3, and 5. But this is very inefficient, since, as the integers get larger, fewer and fewer of them fit the requirement. As an alternative, let us call the required stream of numbers S and notice the following facts about it.
The elements of (scale-stream S 2) are also elements of S.
The same is true for (scale-stream S 3) and (scale-stream 5 S).
These are all the elements of S.
Now all we have to do is combine elements from these sources. For this we define a procedure merge that combines two ordered streams into one ordered result stream, eliminating repetitions:
(define (merge s1 s2)
(cond ((stream-null? s1) s2)
((stream-null? s2) s1)
(else
(let ((s1car (stream-car s1))
(s2car (stream-car s2)))
(cond ((< s1car s2car)
(cons-stream s1car (merge (stream-cdr s1) s2)))
((> s1car s2car)
(cons-stream s2car (merge s1 (stream-cdr s2))))
(else
(cons-stream s1car
(merge (stream-cdr s1)
(stream-cdr s2)))))))))
Then the required stream may be constructed with merge, as
follows:
(define S (cons-stream 1 (merge ?? ??)))Fill in the missing expressions in the places marked ?? above.
Exercise.
How many additions are performed when we compute the nth Fibonacci
number using the definition of fibs based on the
add-streams procedure? Show that the number of additions would be
exponentially greater if we had implemented
(delay exp) simply as (lambda () exp),
without using the optimization provided by the memo-proc
procedure described in section
.
Exercise. Give an interpretation of the stream computed by the following procedure:
(define (expand num den radix) (cons-stream (quotient (* num radix) den) (expand (remainder (* num radix) den) den radix)))(Quotient is a primitive that returns the integer quotient of two integers.) What are the successive elements produced by (expand 1 7 10)
Exercise. In section
we saw how to implement a
polynomial arithmetic system representing polynomials as lists of
terms. In a similar way, we can work with power series, such as
represented as infinite streams.
We will represent the series
as the stream whose elements are the coefficients
a0, a1, a2, a3, ....
a. The integral of the series
is the series
b. The function
is its own
derivative. This implies that ex and the integral of ex are the
same series, except for the constant term, which is e0 =1.
Accordingly, we can generate the series for
ex as
(define exp-series (cons-stream 1 (integrate-series exp-series)))Show how to generate the series for sine and cosine, starting from the facts that the derivative of sine is cosine and the derivative of cosine is the negative of sine:
(define cosine-series (cons-stream 1 ??)) (define sine-series (cons-stream 0 ??))
Exercise.
With power series represented as streams of coefficients as in
exercise
, adding series is implemented by
add-streams. Complete the definition of the following procedure for
multiplying series:
(define (mul-series s1 s2) (cons-stream ?? (add-streams ?? ??)))You can test your procedure by verifying that sin2 x + cos2 x = 1, using the series from exercise
.
Exercise.
Let S be a power series (exercise
)
whose constant term is 1. Suppose we want
to find the power series 1/S, that is, the series X such that
.
Write S=1+SR where SR is the part of S after
the constant term. Then we can solve for X as follows:
.
Exercise.
Use the results of exercises
and
to define a procedure div-series
that divides two power series. Div-series should work for any
two series, provided that the denominator series begins with a
nonzero constant term. (If the denominator has a zero constant term,
then div-series should signal an error.)
Show how to use div-series
together with the result of exercise
to generate
the power series for tangent.