CS 22
Clab 21: Queues, Tables, and an Introduction to Simulation
Queues
Review the material on pp. 261-265 making sure you understand
what a queue is and how your authors' choose to represent one.
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec_3.3.2
It is important to understand how and why all the queue operations
in this implementation take O(1) steps.
Here is the sicp queue code and the definition of and insertion into
a couple of queues:
(define (front-ptr queue) (car queue))
(define (rear-ptr queue) (cdr queue))
(define (set-front-ptr! queue item) (set-car! queue item))
(define (set-rear-ptr! queue item) (set-cdr! queue item))
(define (empty-queue? queue) (null? (front-ptr queue)))
(define (make-queue) (cons '() '()))
(define (front-queue queue)
(if (empty-queue? queue)
(error "FRONT called with an empty queue" queue)
(car (front-ptr queue))))
(define (insert-queue! queue item)
(let ((new-pair (cons item '())))
(cond ((empty-queue? queue)
(set-front-ptr! queue new-pair)
(set-rear-ptr! queue new-pair)
queue)
(else
(set-cdr! (rear-ptr queue) new-pair)
(set-rear-ptr! queue new-pair)
queue))))
(define (delete-queue! queue)
(cond ((empty-queue? queue)
(error "DELETE! called with an empty queue" queue))
(else
(set-front-ptr! queue (cdr (front-ptr queue)))
queue)))
(define q1 (make-queue))
(define q2 (make-queue))
(insert-queue! q1 'a)
(insert-queue! q1 'b)
(insert-queue! q2 'bat)
(insert-queue! q2 'cat)
(insert-queue! q2 'hat)
Play around with the queue operations front-queue, delete-queue!,
insert-queue! on these two queues.
Then do ex 3.21 on p. 265-266.
Tables
As we saw in the number handling package, a table can help us get
around many problems and be used productively in a data-driven system.
We used a two-dimensional table of operations (i.e. functions) whose
rows were indexed by the operation name and whose columns were indexed
by the representation. Here is some code for maintaining a two-dimensional
table.
(define (assoc key records)
(cond ((null? records) #f)
((equal? key (caar records)) (car records))
(else (assoc key (cdr records)))))
(define (make-table)
(let ((local-table (list '*table*)))
(define (lookup key-1 key-2)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
#f))
#f)))
(define (insert! key-1 key-2 value)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! local-table
(cons (list key-1
(cons key-2 value))
(cdr local-table)))))
'ok)
(define (dispatch m)
(cond ((eq? m 'lookup-proc) lookup)
((eq? m 'insert-proc!) insert!)
(else (error "Unknown operation -- TABLE" m))))
dispatch))
(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-table 'insert-proc!))
Here are a couple of test evals.
> (put 'x1 'x11 'val11)
ok
> (put 'x1 'x12 'val12)
ok
> (put 'x2 'x21 'val21)
ok
> (get 'x1 'x11)
val11
> (get 'x3 'x11)
#f
> (get 'x2 'x21)
val21
> (get 'x1 'x12)
val12
So it looks like we can put things in the table and get things out.
The code that maintains the table is a good example of gross-lisp. It
leaves the beauty of the stateless world for the practical advantages
of a world with state. The state changing functions are the ones that
end in ! (pronounced bang by most lispers). Let's take a peek at how
the table is implemented.
The table is pretty well encapsulated. make-table just returns a
function dispatch which internally holds a state-full entity called
local-table. There is no way we can get at it now except through
get and put. So let's break the rules a bit and put a spy function
peek into dispatch function. Here is how.
In the definition of make-table, just before the internal definition
of dispatch add the following internal definition.
(define peek (lambda () (display local-table)))
Add
((eq? m 'peek) peek)
inside the conditional defining dispatch. And add
(define peek (operation-table 'peek))
at the top-level say after the definition of put. Now try a
sequence of puts and peeks. e.g.
> (put 'x1 'x11 'val11)
ok
> (peek)
(*table* (x1 (x11 . val11)))
> (put 'x1 'x12 'val12)
ok
> (peek)
(*table* (x1 (x12 . val12) (x11 . val11)))
> (put 'x2 'x21 'val21)
ok
> (peek)
(*table* (x2 (x21 . val21)) (x1 (x12 . val12) (x11 . val11)))
> (put 'x1 'x13 'val13)
ok
> (peek)
(*table* (x2 (x21 . val21)) (x1 (x13 . val13) (x12 . val12) (x11 . val11)))
>
You can see that, except for the header, the table is a list of lists.
Each sublist
represents a row whose car is the name of the row. Within a row,
each column is represented by a dotted pair whose car is the column
name and whose cdr is the table value for that row and column.
Simulation
In his rush for class, professor K. wrote a somewhat weak simulation
program to illustrate the use of queues.
In fact, he was so excited about discovering the looping command
do and the let* construction (check them out in help) that
at first he forgot
to use queues at all. Here is his first attempt.
;; real-random by cfk is a parameterless function that returns
;; an inexact random number uniformly distributed between 0 and 1
(define real-random (lambda ()
(/ (random (- (expt 2 31) 1))
(exact->inexact (expt 2 31)))))
;; randomrange by cfk is a function that takes two parameters
;; min and max and returns
;; an inexact random number uniformly distributed between min and max
(define randomrange
(lambda (min max)
(+ min (* (real-random) (- max min)))))
(define (simulate prob minsvctim maxsvctim totsimtim)
(define (newcust) (< (real-random) prob))
(let ((timinq 0)
(numcustserved 0)
(custslost 0)
(p1tim 0))
(define (outputstats) ;;;output summary stats
(begin
(display "timinq = ") (display timinq)
(newline)
(display "numcustserved = ") (display numcustserved )
(newline)
(display "custslost = ") (display custslost)
(newline)
))
(define (nextminute simtim) ;;;do sim for next minute
(let* ((newc (newcust))
(svctim (randomrange minsvctim maxsvctim)))
(begin
(if (<= p1tim simtim) ;;;p1 avail
(if newc
(begin
(set! numcustserved (+ 1 numcustserved))
(set! p1tim (+ simtim svctim))
(set! newc #f)
))
;;; p1 not available
(if newc
(set! custslost (+ 1 custslost)))
))))
(do ((simt 0 (+ 1 simt)))
((>= simt totsimtim) (outputstats))
(nextminute simt)
)
))
(simulate 0.5 3 5 100)
This is really bad. The lack of meaningful comments means Prof. K
will have to explain some of this in class. Of course if he was
grading he would have to take off lots.
After getting over his excitement about do and let*, Professor K
wrote a slightly improved simulation.
;; you need the sicp queue stuff.
;; real-random by cfk is a parameterless function that returns
;; an inexact random number uniformly distributed between 0 and 1
(define real-random (lambda ()
(/ (random (- (expt 2 31) 1))
(exact->inexact (expt 2 31)))))
;; randomrange by cfk is a function that takes two parameters
;; min and max and returns
;; an inexact random number uniformly distributed between min and max
(define randomrange
(lambda (min max)
(+ min (* (real-random) (- max min)))))
(define (makecust arrtim stim) (cons arrtim stim))
(define (arrtim cust) (car cust))
(define (svctim cust) (cdr cust))
;;; simulate simulates a gas station with one pump
;;; prob is the probablility that a customer shows up in any one minute
;;; minsvctim is the minimum time to service a customer
;;; maxsvctim is the maximum time to service a customer
;;; totsimtim is the total number of minutes to run the simulation for.
;;;
(define (simulate prob minsvctim maxsvctim totsimtim)
(define (newcust) (< (real-random) prob))
(let ((timinq 0)
(numcustserved 0)
(custslost 0)
(p1tim 0)
(p1q (make-queue)))
(define (outputstats) ;;;output summary stats
(begin
(display "timinq = ") (display timinq)
(newline)
(display "numcustserved = ") (display numcustserved )
(newline)
(display "custslost = ") (display custslost)
(newline)
))
(define (nextminute simtim) ;;;do sim for next minute
(let* ((newc (newcust))
(svtim (randomrange minsvctim maxsvctim))
(thiscust (makecust simtim svtim)))
(begin
(if (<= p1tim simtim) ;;;p1 avail
(cond
((not (empty-queue? p1q)) ;;; move cust in from queue
(let ((qperson (front-queue p1q)))
(delete-queue! p1q)
(set! timinq (+ timinq (- simtim (arrtim qperson))))
(set! numcustserved (+ 1 numcustserved))
(set! p1tim (+ simtim (svctim qperson)))
))
(else ;;; p1 queue is empty
(if newc ;;;move right to pump
(begin
(set! p1tim (+ simtim svtim))
(set! numcustserved (+ 1 numcustserved))
(set! newc #f)
)))))
(if newc ;;; if still customer waiting put in queue
(insert-queue! p1q thiscust)
))))
(do ((simt 0 (+ 1 simt)))
((>= simt totsimtim) (outputstats))
(nextminute simt)
)
))
(simulate 0.5 3 5 100)
It's still a little lame. Your job is to fix it up a little. The
documentation is so poor, the professor will have to say a few words
in class, but it would be a good idea for you to look this over before
the few words.
The overall idea is this: We have a gas station with one pump. In any
given minute, with probability prob, a driver comes along who wants
gas. If the pump is available the car will pull in and buy gas. If
the line for the pump is longer than 3 cars, the car will go to
another gas station and be a customer lost. If the car wants gas and
there are 3 or fewer cars in the line for the pump, the car will get
in line.
Professor K's simulation is a start. Since he didn't
have a function to find the length of a queue, he just added cars to
the queue. Due to an oversight, when he printed statistics, he forgot
to count time in the queue for customers who were still waiting at the
end of the simulation time. Pretty lame!! Fix this up, please. If there
is time, show
me what you get. If you want some fun, after
you have the one pump simulation working properly, you
might do 2 pumps with separate queues for each pump. Then try
2 pumps with one common queue. Or generalize to n pumps with
n separate queues and n pumps with a common queue.