CISC-280 Program Development Techniques

Homework 9: Mutators, Concurrency, Streams

Due: Tuesday, December 9, 2008

Problem 1 - 50 points

Use language Essentials of Programming Languages. 

The builtin procedure length works correctly on proper lists, but will go into an infinite loop on lists with a loop back. For instance, this sequence of Scheme commands will go into an infinite loop:

(define L '(1 2 3 4 5))
(define M (cddddr L))
(set-cdr! M (cddr L))
(length L)

The resulting L is a structure with 5 pairs. The car's are the numbers 1 through 5. Let us identify the pairs by their cars. The mutation done by the set-cdr! makes the cdr of pair 5 be pair 3. Thus when length "walks down" L it visits the pairs in the order

1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, ....

As a result, length never terminates. L consists of an initial segment 1, 2 followed by a circle 3, 4, 5. Write a procedure clength that takes an arbitrary list structure and returns its length if it is a list without loop back. If there is a loop back, clength returns a list of two numbers (m n) where m is the number of pairs in the initial segment of the input and n is the number of pairs in the circle caused by the loop back. For the list structure L above, (clength L) returns the list (2 3).

Hints

First write a definition for clength that just computes the length of an ordinary list using an auxiliary procedure that runs as an iterative process. Then modify your auxiliary procedure to maintain a list of the pairs that have been seen already as it walks down the original list. You may use memq to test for membership on a list or write your own function using eq?. Do not use equal? or member (which uses equal?). If two of the cars of pairs in the input list have similar list structures that are themselves circular lists, equal? will never terminate. You may use length if you wish but be certain that the list you apply it to is guaranteed not to contain a loop back. (The input list might; that is why we are writing clength in the first place.)

Problem 2 - 10 Points

Problem 3.39 from the text (page 306) - serialize execution results.

Problem 3 - 20 Points

For this problem, use language Pretty Big.
1. In the file sellers.ss you will find code for simulating ten ticket sellers. Initially, there are 100 tickets, but if you run this simulation, you will find that these ticket sellers will sell more than 100 tickets. Furthermore, when they report how many tickets they sold, their reports get all mixed up.

The file sellers.scm contains all the code you need to run the simulation. It includes the make-serializer procedure that you will need for the solution. It defines my-sleep which is a function that is like the built-in sleep function, but that allows you to run the simulation faster or slower for testing. The speed is controlled by the global variable sleep-factor.

To run the simulation, load sellers.scm into DrScheme and run (test). If some of the ticket sellers don't sell any tickets, increase the value of global variable sleep-factor. If it takes more than a few seconds for (test) to run, make sleep-factor smaller. (On Strauss, sleep-factor probably should be about 1000. On my laptop a value of 20000 is about right.)

When (test) is run, the global variable *SEATS-LEFT* is set to 100, ten ticket sellers are created by calls to make-ticket-seller and run as concurrent threads. The calls to my-sleep with a random argument assures that the threads proceed in an unpredictable way so that they don't update *SEATS-LEFT* properly until you fix the problem.

2. You are to change the definition of make-ticket-sellers so that the ticket sellers only sell 100 tickets and so that their reports don't get mixed up. Do not change any other code in sellers.scm. You will need to add a small amount of additional code besides changing the definition of make-ticket-seller. Here is the definition of make-ticket-seller:

(define (make-ticket-seller name)
(let ((total-tickets-sold 0))
(define (sell)
(sleep (random 2)) ;sleep up to two seconds until customer arrives
(if (customer-order *SEATS-LEFT*)
(begin (set! *SEATS-LEFT* (- *SEATS-LEFT* 1)) ;record the sale globally
(set! total-tickets-sold (+ total-tickets-sold 1)) ; local record
(sell)) ;continue selling
(print-tickets-sold name total-tickets-sold)
))
sell))
Note that there are two areas where serialization is necessary. One is where ticket seller accesses *SEATS-LEFT* and updates it, and the other is where it makes its report about how many tickets it has sold. You have to make changes that serialize both areas. You should serialize as little code as you can, however.

Caution!

Procedure parallel-execute has been defined in this program so that all ten ticket seller threads run concurrently and all must terminate before control returns to the top-level read-eval-print loop. If one of your ticket sellers causes a bug message to appear (you will see a lady-bug icon), nothing in DrScheme will appear to work until you click on the Break button.

Problem 4 - 25  Points

For this problem, use language Pretty Big.

This problem has you write two functions dealing with streams: stream-merge and stream-print-n.

Write a procedure, stream-merge, that can join two (possibly infinite) increasing numeric streams. Duplicates should remain in the result.

The basic stream implementation that you will need is provided in file streams.ss.

Procedure stream-merge returns a stream. Do not modify any stream utilities, or create any new versions of them.

To facilitate testing, four streams are already defined for you. Streams fstream1 and fstream2 are two short arbitrary streams of increasing integers. Stream lstream1 is a long stream of even integers, and stream lstream2 is a long stream of integers that are divisible by 3. The procedure stream-display has been defined  in streams.ss. To facilitate testing of you stream-merge function, you should write a second procedure for displaying the first n numbers in a stream. The function should be called stream-print-n and it takes a stream and a number. It displays the first number elements of the input stream. If your stream-merge procedure is defined properly, you should get the following results:

>(display-stream (stream-merge fstream1 fstream2))
[1 1 2 3 3 4 5 5 5 6 7 7 8 ]
> (stream-print-n (stream-merge lstream1 lstream2) 20)
[0, 0, 2, 3, 4, 6, 6, 8, 9, 10, 12, 12, 14, 15, 16, 18, 18, 20, 21, 22]
> 1 1 2 3 3 4 5 5 5 6 7 7 8

Don't forget to make sure that your stream-merge procedure works properly when one or both of its arguments is the-empty-stream.