Streams of numbers

Chapter: Streams of numbers

Do you like numbers? Do you often look at yourself in the bathroom mirror and say, "I wish I could have a list of every number in the universe"? You must've been thrilled by the Haskell examples above. Here's more good news: you can do this in Scheme! By using streams, you'll be able to represent infinite sets! Remember, this is possible only because streams use a lazy cons$ to avoid evaluating all their arguments. A stream of integers does not need to compute all the integers it will represent at run-time. It only needs to use cons$ to create the stream, and car$ and cdr$ to look at the stream. Nothing will be generated until it is needed. Here is a function that defines the stream of integers implicitly:

   (define integer$
     (lambda (x)
       (cons$ x (integer$ (add1 x)))))

You can use printn$ or print$ (which you loaded in from the file stream.ss) to examine your stream, like this:

 > (define zippy (integer$ 1))
 > (print$ zippy)
 1 2 3 4 5 6 7 8 9 10
 Want more? no
 done
 > zippy 
 (1 . #)

A good way to produce streams of numbers is to write a function function which produces the next number in the stream given the current number. That is the technique used just now in integer$: A call to (integer$ n) does a cons$ of n onto a recursive call to integer$ passing it the next number (+ n 1).

Q. 11
Using the technique just described, write a function fourmults so that (fourmults 0) will produce the stream 0,4,8,12,16,...


Q. 12
Now write a procedure (scale$ s n) which produces the stream consisting of the elements of s multiplied by n.


.

You may be familiar with a result from mathematics that asserts that the positive rational numbers are countable. The proof usually given in mathematics courses involves laying out the rationals in an imagined grid pattern:


1/1 1/2 1/3 1/4 1/5 1/6 1/7 ...
2/1 2/2 2/3 2/4 2/5 2/6 2/7 ...
3/1 3/2 3/3 3/4 3/5 3/6 3/7 ...
4/1 4/2 4/3 4/4 4/5 4/6 4/7 ...
5/1 5/2 5/3 5/4 5/5 5/6 5/7 ...
6/1 6/2 6/3 6/4 6/5 6/6 6/7 ...
7/1 7/2 7/3 7/4 7/5 7/6 7/7 ...
 .   .   .   .   .   .   .

This is actually a bit of an overkill, since some rationals appear more than once in slightly different guises. For example 4/2 is the same rational as 2/1 or 6/3. But never mind that.

To establish that this set of numbers is countable, mathematicians start listing the rationals by following the diagonal paths:

The first diagonal has just one rational number 1/1.

The second has 2/1 and 1/2.

The third has 3/1 2/2 1/3.

The fourth has 4/1 3/2 2/3 1/4.

and so on.

By enumerating the rationals in the order suggested by following the successive diagonals, the mathematicians can be sure that their list covers all the rationals.

Q. 13
What is the formula for obtaining the next rational nextx/nexty from the current rational currentx/currenty?


So now we can generate the stream of all the positive rationals in this diagonal order:

(define ratsfrom
  (lambda (r)
    (cons$ r (ratsfrom (nextrat r)))))
(define rats (ratsfrom (cons 1 1)))


Exercise 3

Modify the function nextrat so that the stream of rationals produced by (ratsfrom (cons 1 1)) will be in the order given by traversing those same diagonals but from top right to bottom left instead of the present bottom left to top right?

The new code should work like this:

> (print$ rats)
(1 . 1) (1 . 2) (2 . 1) (1 . 3) (2 . 2) (3 . 1) (1 . 4) (2 . 3) (3 . 2) (4 . 1) 
Want more? y
(1 . 5) (2 . 4) (3 . 3) (4 . 2) (5 . 1) (1 . 6) (2 . 5) (3 . 4) (4 . 3) (5 . 2) 
Want more? n
done
> 



Here is another neat problem that can be solved easily with streams.


Exercise 4

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. Now all we have to do is combine elements from these sources.
  1. Write (merge$ s1 s2), which combines two ordered streams s1 and s2 into one ordered result stream, eliminating repetitions.
  2. Construct the desired stream S.
You should be able to do the following with S:
> (print$ S)
1 2 3 4 5 6 8 9 10 12 
Want more? y
15 16 18 20 24 25 27 30 32 36 
Want more? y
40 45 48 50 54 60 64 72 75 80 
Want more? y
81 90 96 100 108 120 125 128 135 144 
Want more? n

done
> 



Power Series

Streams can be used to model the power series that define important mathematical functions. Among these are the following that compute the exponential, sine and cosine functions:



We will represent the series a0 + a1 x + a2 x2 + a3 x3 + ··· as the stream whose elements are the coefficients a0, a1, a2, a3, ....:

e-series$ = (1/0! 1/1! 1/2! ... 1/n! ... )
sin-series$ = (0 1/1! 0 -1/3! 0 1/5! ... )
cos-series$ = (1 0 -1/2! 0 1/4! 0 -1/6! ...)

To define these streams we start by building the stream of factorials. First we need *$, defined analogously to +$: it takes 2 numerical streams and multiplies them element by element

(define *$
  (lambda (s1 s2)
     (cons$ (* (car$ s1) (car$ s2)) (*$ (cdr$ s1) (cdr$ s2)))))
Using the recurrence relation (n + 1)! = (n + 1) * n!, we build the factorial stream using (integer$ 1) = 1 2 3 ... analogously to the fibonacci stream shown in class.

   fact-stream$  = 1    1      (2 * 1)     (3 * 2 * 1)   ...
*$ (integer$ 1)  = 1    2         3             4        ...
                -------------------------------------------------
                   1 (2 * 1) (3 * 2 * 1) (4 * 3 * 2 * 1) ...
The last line is just (cdr$ fact-stream$). Consequently we can define fact-stream$ as follows.
(define fact-stream$
  (cons$ 1 (*$ fact-stream$ (integer$ 1))))
Now we can build our power series.


Exercise 5

The e-series$ can be built by applying the following function to the stream of factorials (try it!):
(define e-summands
  (lambda (s)
    (cons$ (/ 1 (car$ s)) (e-summands (cdr$ s)))))

(define e-series$ (e-summands (fact-stream$)))
Define sin-series$ and cos-series$ similarly by first defining functions sin-summands and cos-summands that use the fact-stream$.



There is actually an implicit definition for these streams based on their "calculus" properties.


Exercise 6



Let's compute something

In order to actually compute ex, sin(x) or cos(x) for some x, we need to insert the powers of x into the series and form the stream of partial sums: a0, a0 + a1x, a0 + a1x + a2x2, ... The first part is easy: we define a stream of powers using powers$, then use *$ on that stream and our exponential, sine and cosine streams:

(define powers$
  (lambda (x n)
    (cons$ (expt x n) (powers$ x (add1 n)))))

(define etodax-stream$
  (lambda (x)
     (*$ e-series$ (powers$ x 0))))

(define sinx-stream$
  (lambda (x)
     (*$ sin-series$ (powers$ x 0))))

(define cosx-stream$
  (lambda (x)
     (*$ cos-series$ (powers$ x 0))))
Now we need to form the stream of partial sums. Given a series s0, s1, s2 ... , we note that if we form the series of partial sums on the tail of the series, s1, s2 ... , we get s1, s1 + s2, .... If we add s0 to each element we get the series s0 + s1, s0 + s1 + s2, ..., which is the tail of the series of partial sums of the original series. This motivates the following definition:
(define partial-sums
  (lambda (s) 
     (cons$ (car$ s) 
            (map$ (lambda (z) (+ (car$ s) z)) (partial-sums (cdr$ s))))))


Exercise 7

Do not hand this one in.

Define

(define etodax$
  (lambda (x) (partial-sums (etodax-stream$ x))))

(define sinx$
  (lambda (x) (partial-sums (sinx-stream$ x))))

(define cosx$
  (lambda (x) (partial-sums (cosx-stream$ x))))
Form (etodax 3), (sinx 1.57), etc. and use show-series (see below) to look a the decimal expansions of the partial sums. Compare with the system versions of sin, cos and exp. How long a partial sum is required to obtain an approximation good to various decimal places?
(define show-series$
  (lambda (n s)
    (show$ n (map$ exact->inexact s))))



rms@cs.oberlin.edu