CSCI 275

Lab 8: Streams
Due Friday, May 8

In this lab we will be working with streams. A stream is a data structure made possible by a new version of cons called cons$, which does not evaluate its second argument. Thus a stream consists of a head element, which can be accessed in the usual way using the car$ function and a tail which is a "promised" stream. The tail is accessed via the cdr$ function which forces evaluation of the original second argument to cons$.

You can think of streams as infinite lists which are manipulated with car$, cdr$, and cons$ in much the same way as finite liss are manipulated with car, cdr, and cons.

Here are the files you need to download for this lab:      streams.rkt    keyboard.rkt

Part 1: Streams

A stream is a list-like structure, with some of the values actually present and the rest contained in a promise.   This can be used to represent infinite sequences.  For example, we might  have a stream of positive integers:

  (1 2 3 <promise>)

Each time we evaluate the promise we get the next value of the stream, and another promise to produce the rest:

            (1 2 3 4 <new promise>)

Streams are built from three primitive operators:

These three are contained in file "streams.rkt" which we will use for all of our work with streams. You want to download the file streams.rkt and put the line

(require "streams.rkt")

at the top of any Dr. Racket file using streams.

In addition to car$, cdr$ and cons$, streams.rkt defines a number of helper procedures for working with streams:

Here are a few examples:

Here is another, more subtle definition of the integers starting from 1:

The first elements of Ints1$ is certainly 1, since we cons$ 1 onto the front of the stream. The next element is the result of adding the first element of Ones$ (i.e, 1) onto the first element of Ints1$, which we just saw is also 1; so the second element is 2. The next elment is the result of adding 1 onto this second element to get 3, and so forth.


 

Exercise 1:  Write the functions

·        (rember-all$ x s); returns a stream with all occurrences of x removed from the stream s

·        (subst-all$ x y s); returns a stream with all occurrences of x in the stream s replaced with

You can test these out on the following stream:

 (define Tester$ (cons$ 1 (cons$ 2 (cons$ 3 Tester$))))

 

Part 2: Mathematical Streams

Remember our stream construction of the integers:

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

Here is a challenge. Consider the following grid of pairs of numbers:

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 ...  

 .   .   .   .   .   .   . 

Note that we can display a.b with (cons a b). Of course, it is easy to make the first row of this grid as a stream, but if we try to print the grid row-by-row we will never finish with the first row. A better approach is to enumerate the elements diagonally. The first diagonal (right-to-left) contains only 1.1 The next diagonal contains 1.2 and 2.1 The next 1.3  2.2 and 3.1, and so forth.

We can define this stream of pairs pretty easily, the same way we define our stream of integers:

(define pairsFrom$  (lambda (p)
          (cons$ p (pairsFrom$ (nextPair p)))))

(define allPairs$ (pairsFrom$ (cons 1 1)))

Of course there is a missing piece here, which you will fill in on the next exercise:

Exercise 2:  Write procedure (nextPair p) that takes one of the pairs for this enumeration and returns the next pair. This is not itself a stream function; it is just what we use to make our stream of pairs.  If you make nextPair correctly the allPairs$ stream given above will work.

Exercise 3: Hamming sequences

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 Ham$ 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 Hamming stream Ham$.  Here are the first few elements:

> (print$ Ham$) 

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  >

Part 3: 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:

 
 

Suppose we generate the following streams of coefficients:

e-coeffs$ = (1/0! 1/1! 1/2! ... 1/n! ... ) 

sin-coeffs$ = (0 1/1! 0 -1/3! 0 1/5! ... ) 

cos-coeffs$ = (1 0 -1/2! 0 1/4! 0 -1/6! ...)
(Powers$ x) = (1   x   x2   x3   x4 ....)

 

Mathematicians use the term "Partial Sums" for the sequence of sums of the first n terms of a sequence. For the sequence a b c d e ... the partial sums are a  a+b  a+b+c   a+b+c+d ..... We can write a procedure to make the stream of Partial Sums of the stream s:

              

(define PartialSums$  (lambda (s)

                   (cons$ (car$ s) (+$ (PartialSums$ s) (cdr$ s)))))

                              

Then 

(PartialSums$ (*$ (Powers$ x) sin-coeffs$)) 
gives increasingly good approximations to sin(x), and the other series act similarly.

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)))))

 

      

Now notice that


         fact-stream$  = 1        1         (2 * 1)            (3 * 2 * 1)   ... 

   *$ (IntsFrom$ 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$ (IntsFrom$ 1)))) 

Now we can build our power series.

The e-coefffs$ can be built as follows::
               (define e-coeffs$ (cons$ 1 (map$ (lambda (x) (/ 1.0 x)) fact-stream$)))

For example, the first few terms of the e-coeffs$ are

1  1  1/2  1/6  1/24  1/120  1/720  1/5040  1/40320  1/362880

The following function:

 (define E (lambda (x) (PartialSums$ (*$ (Powers$ x) e-coeffs$)))) 

gives increasingly good approximations to the values of the exponential function. For example, (E 1) gives values 1  2.0  2.5  2.66  2.708  2.7166  2.7180  2.71825 ... Thanks to being bored once in high school I know that the correct value to 14 decimal places is 2.71828182845904...

Exercises 4 and 5:

Produce the streams

sin-coeffs$ = (0  1/1!  0  -1/3!  0  1/5! ... ) 

cos-coeffs$ = (1  0  -1/2!  0  1/4!  0  -1/6! ...) 

 

For these exercises you only need to define the coefficient streams, but if you make the further definitions:

 

(define sin (lambda (x) (PartialSums$ (*$  (Powers$ x) sin-coeffs$))))
(define cos (lambda (x) (PartialSums$ (*$  (Powers$ x) cos-coeffs$))))

 

 

you should get increasingly accurate approximations to sin(x) and cos(x).

 

Part 4: Filtering

File keyboard.rkt has code that makes a keyboard-stream. This is a stream of whatever s-expressions you type. There is also an output$ function that takes a stream and outputs it one element at a time. Together these set up a communication channel:

(output$ (keyboard-stream))

echoes whatever you type, and continues until you click on the "eof" box at the right. We will use this in the next set of exercises:

Grune's Problem

There is a famous problem due to Grune that is important in data communications. Consider a stream of characters, which we will model by typing single characters followed by a carriage-return in response to keyboard-stream's prompts. Most characters we will allow to pass through unaltered. But if we ever receive two a's in a row, then we will pass through just a single b and no a.

Exercise 6:  Write the filter grune-a-b to work as follows:

> (output$ (grune-a-b (keyboard-stream))) 
? a 
? b 
a 

b 
? c 
c 

? d 

d 

? a 

? a 

b 

? a 

? b 

a 

b 

? a 

? a 

b 

? <clicks eof> 

done  >  

Exercise 7: Now write a similar function, but abstract the a and b. Write a function grune which takes two arguments and produces a filter similar to grune-a-b but with the first argument taking the role of a and the second taking the role of b. Your code should start:

 

                               (define grune    (lambda (a b)  

where now a and b are variables. (grune 'x 'y) needs to return a function, similar to grune-a-b which works as a stream filter replacing two successive x by a single y. Here is a transcript of grune in action:

> (output$ ((grune 'x 'y) (keyboard-stream))) 

? a 

a 

? b 

b 

? c 

c 

? d 

d 

? x 

? y 

x 

y 

? a 

a 

? b 

b 

? x 

? x 

y 

? x 

? x 

y 

? a 

a 

? b 

b 

? <clicks eof> 

done  >  

Notice that (grune 'a 'b) has the same functionality as grune-a-b.Grune's problem becomes interesting when we consider pipelining several Grune operations. If you did the last exercise correctly, you should be able to chain several "grune"s together:

> (output$ ((grune 'c 'd) ((grune 'b 'c) ((grune 'a 'b) (keyboard-stream)))))   

? a 

? b 

a 

? c 

b 

? d 

c 

d 

? e 

e 

? f 

f 

? g 

g 

? a

? a 

? a 

? a 

? a 

? a 

? a 

? a 

d 

? a 

? a 

? a 

? a 

? x 

c 

x 

? y 

y 

? z 

z 

? x 

x 

? y 

y 

? z 

z 

? <clicks eof> 

done  >