Title: Abstract Data Types
1Lecture 15
Abstract Data Types Streams (We will only start
this topic)
2Wed Lecture and Friday Midterm
- Notice Wed lecture will be rehearsal for
midterm - Exam on Friday starts at 9AM
3Last lecture
- The environment modela new model to explain the
semantics of our language - New model provides notion of state
- Allows us to introduce mutation
4Stack Data Abstraction
Last in, First out.
Insert
Delete
5Stack Data Abstraction
- constructor (make-stack) returns an empty
stack - selectors (top stack) returns current top
element from a stack - operations (insert stack elem) returns a new
stack with the element added to the top of
the stack (push) - (delete stack) returns a new stack with the
top element removed from the stack (pop) - (empty-stack? stack) returns t if no elements,
f otherwise - contract
- (delete stack) require (not (empty-stack? stack))
- (top stack) require (not (empty-stack? stack))
- ensure (equal? (delete (insert stack elem))
stack) - ensure (equal? (top (insert stack elem)) elem)
6Stack Implementation Strategy
- implement a stack as a list
d
b
a
- we will insert and delete items at the front of
the stack
7Functional Stack Implementation
- (define (make-stack) nil)
- (define (empty-stack? stack) (null? stack))
- (define (insert stack elem) (cons elem stack))
- (define (delete stack)
- (if (empty-stack? stack)
- (error "stack underflow delete")
- (cdr stack)))
- (define (top stack)
- (if (empty-stack? stack)
- (error "stack underflow top")
- (car stack)))
8Limitations in our Stack
- Stack does not have identity
- (define s (make-stack))
- s gt ()
- (insert s 'a) gt (a)
- s gt ()
- (set! s (insert s 'b))
- s gt (b)
9Mutable Stack Implementation
The stack will be a mutable data type.
- The data type contains a list of elements as
before. - Insert and delete mutate a stack object.
- The first element of the list is special to
distinguish - Stack objects from other lists defensive
programming
10Mutable Stack Data Abstraction
- constructor (make-stack) returns an empty
stack - queries (selectors) (top stack) returns
current top element from a stack (empty-stack?
stack) returns t if no elements, f otherwise - (stack? any) returns t if any is a
stack, f otherwise - command (mutators, transformers) (insert!
stack elem) modify the stack by adding
elem to the top of the stack (push) - (delete! stack) modify the stack by removing
the top element from the stack (pop)
stack? is really not part of the stack abstraction
11Mutable Stack Implementation (1)
- (define (make-stack) (cons 'stack nil))
(define (stack? stack) (and (pair? stack) (eq?
'stack (car stack))))
(define (empty-stack? stack) (null? (cdr
stack)))
(define (top stack) (if (empty-stack? stack)
(error "stack underflow top") (cadr
stack)))
12Mutable Stack Implementation (2)
- (define (insert! stack elem)
- (set-cdr! stack (cons elem (cdr stack)))
- stack))
- (define (delete! stack)
- (if (empty-stack? stack)
- (error "stack underflow delete")
- (set-cdr! stack (cddr stack)))
- stack)
Here the mutators return the stack. May choose to
return a neutral value, eg. ok or nothing (more
later)
13Mutatable vs. functional
- The decision between a mutable stack and a
functional stack is part of the contract - Changing this changes the abstraction!
- The user should know if the object mutates or not
in order to use the abstraction correctly - For example, if we write
- (define stack1 stack2)
- can stack1 later change because of changes to
stack2 ?
14A Queue
FIFO First In, First Out
15A Queue
Insert
Insert
FIFO First In, First Out
Insert
x3
x2
Delete
16A Queue Implementation
- A queue is a list of elements
- The front of the queue is the first element in
the list - To insert an element at the tail of the queue,
need to scan the entire list, then attach the new
element at the rear
d
new
c
b
17A Queue Implementation (Cont)
- (define (make-queue) null)
- (define (empty-queue? q) (null? q))
- (define (front-queue q)
- (if (empty-queue? q)
- (error "front of empty queue" q)
- (car q)))
- (define (delete-queue q)
- (if (empty-queue? q)
- (error "delete of empty queue" q)
- (cdr q)))
- (define (insert-queue q elt)
- (if (empty-queue? q)
- (cons elt nil)
- (cons (car q) (insert-queue (cdr q) elt))))
18Complexity of the implementation
- For a queue of length n
- Time required -- number of cons, car, cdr calls?
- Space required -- number of new cons cells?
-
- front-queue, delete-queue
- Time T(n) T(1) that is, constant in time
- Space S(n) T(1) that is, constant in space
- insert-queue
- Time T(n) T (n) that is, linear in time
- Space S(n) T(n) that is, linear in space
19A more efficient implementation
- In order to make both insert and delete efficient
- we need constant time access to the front and the
rear of the queue. - we can achieve this if we keep a pointer to the
front and the rear of the queue. - A mutable implementation.
20Mutable Queue Data Abstraction
- constructor (make-queue) returns an empty
queue - queries (selectors, assessors)
- (front-queue q) returns the object at the
front of the queue. If queue is empty
signals error - (empty-queue? q) tests if the queue is empty
- commands (mutators, transformers)
(insert-queue! q elt) inserts the elt at the
rear of the queue and returns ok - (delete-queue! q) removes the elt at the front
of the queue and returns ok - additional query
- (queue? q) tests if the object is a queue
-
21Implementation
- We attach a type tag as before.
- Maintain queue identity
- Build a structure to hold
- a list of items in the queue
- a pointer to the front of the queue
- a pointer to the rear of the queue
22Queue Helper Procedures
- Hidden inside the abstraction
- (define (front-ptr q) (cadr q))
- (define (rear-ptr q) (cddr q))
- (define (set-front-ptr! q item)
- (set-car! (cdr q) item))
- (define (set-rear-ptr! q item)
- (set-cdr! (cdr q) item))
23Queue implementation
- (define (make-queue)
- (cons 'queue (cons null null)))
- (define (queue? q)
- (and (pair? q) (eq? 'queue (car q))))
- (define (empty-queue? q)
- (if (not (queue? q))
- (error "object not a queue" q)
- (null? (front-ptr q))))
- (define (front-queue q)
- (if (empty-queue? q)
- (error "front of empty queue" q)
- (car (front-ptr q))))
24Queue implementation Insert
- (define (insert-queue! q elt)
- (let ((new-pair (cons elt nil)))
- (cond ((empty-queue? q)
- (set-front-ptr! q new-pair)
- (set-rear-ptr! q new-pair)
- ok)
- (else
- (set-cdr! (rear-ptr q) new-pair)
- (set-rear-ptr! q new-pair)
- ok))))
25Queue implementation - delete
- (define (delete-queue! q)
- (cond ((empty-queue? q)
- (error "delete of empty queue" q))
- (else
- (set-front-ptr! q (cdr (front-ptr q)))
- ok)))
26Time and Space complexities ?
O(1)
27Programming Styles Procedural vs.
Object-Oriented
- Procedural programming - Organize system around
procedures that operate on data - (do-something ltdatagt ltarggt ...)
- (do-another-thing ltdatagt)
- Object-based programming - Organize system
around objects that receive messages - ((ltobjectgt 'do-something) ltarggt)
- ((ltobjectgt 'do-another-thing) ... )
- An object encapsulates data and operations
- Message passing and returned procedures are
the means to write object oriented code in scheme
28Stacks in OO style
(define (make-stack) (let ((top-ptr '()))
(define (empty?) (null? top-ptr)) (define
(delete!) (if (null? top-ptr)
(error . . .) (set! top-ptr (cdr
top-ptr))) top-ptr ) (define (insert!
elmt) (set! top-ptr (cons elmt top-ptr))
top-ptr) (define (top) (if (null?
top-ptr) (error . . .) (car
top-ptr))) (define (dispatch op) (cond
((eq? op 'empty?) empty?) ((eq? op 'top)
top) ((eq? op 'insert!) insert!)
((eq? op 'delete!) delete!))) dispatch))
29Stacks in OO style
(define s (make-stack)) ((s 'insert!) 'a)
gt ((s 'insert!) 'b) gt ((s 'top)) gt ((s
'delete!)) gt ((s 'top)) gt ((s 'delete!)) gt
(a)
(b a)
b
(a)
a
()
- compare with message passing examples in OO we
do not hide behind a functional layer eg. - (define (insert! s a) ((s insert!) a))
- In OO programming languages the notation is eg.
- s.insert(a)
- s.delete()
30Streams Motivation
(define (sum-primes a b) (define (iter count
accum) (cond ((gt count b) accum)
((prime? count) (iter ( count 1)
( count accum))) (else
(iter ( count 1) accum)))) (iter a 0))
(define (sum-primes a b) (accumulate
0 (filter prime?
(enumerate-interval a b))))
Second implementation consumes a lot of storage
31Streams Motivation (Cont.)
(car (cdr (filter prime?
(enumerate-interval 10000 1000000))))
Requires a lot of time and space
How can we gain the efficiency of iteration, and
retain the elegance of sequence-operations?
Streams!
32Remember normal order evaluation?
- Normal (Lazy) Order Evaluation
- go ahead and apply operator with unevaluated
argument subexpressions - evaluate a subexpression only when value is
needed - to print
- by primitive procedure (that is, primitive
procedures are "strict" in their arguments) - Compromise approach give programmer control
between normal and applicative order. - Streams lists with delays
33Streams interface
Constructors the-empty-stream (cons-stream x y)
Selectors (stream-car (cons-stream x y)) x
(stream-cdr (cons-stream x y)) y
Predicate (stream-null? x)
cons-stream treats its second argument as a
delayed object.
34Streams via delay and force
(cons-stream ltagt ltbgt) is a special form
equivalent to (cons ltagt (delay ltbgt))
(define (stream-car stream) (car
stream)) (define (stream-cdr stream) (force (cdr
stream)))
35 delay and force
(delay ltexpgt) gt a promise to evaluate
exp (force ltdelayed objectgt) gt evaluate the
delayed object and return the result
(define x (delay ( 1 1))) x ? ltpromisegt (force
x) ? 2
(delay ltexpgt) is a special form. force is not a
special form.
36What are these mysterious delay and force ?
delay is a special form such that (delay ltexpgt)
is equivalent to (lambda () ltexpgt) force is a
procedure that calls a procedure produced by
delay (define (force delayed-object)
(delayed-object))
37Let us recall lists
(define (enumerate-interval low high) (if (gt
low high) () (cons low
(enumerate-interval ( low 1) high))))
(define int123 (enumerate-interval 1
3)) (enumerate-interval 1 3) (cons 1
(enumerate-interval 2 3)) (cons 1 (cons 2
(enumerate-interval 3 3))) (cons 1 (cons 2 (cons
3 (enumerate-interval 4 3))) (cons 1 (cons 2
(cons 3 ())))
38Enumerating with streams
(define (stream-enumerate-interval low high)
(if (gt low high) the-empty-stream
(cons-stream low (stream-enumerate-i
nterval ( low 1) high))))
(define s (stream-enumerate-interval 1
3)) (stream-enumerate-interval 1 3) (cons-stream
1 (stream-enumerate-interval 2 3)) (cons 1 (delay
(stream-enumerate-interval 2 3))) (cons 1 (lambda
() (stream-enumerate-interval 2 3)))
39(define s (stream-enumerate-interval 1 3)) GE
stream-enumerate-interval
GE
plow high b (if (gt low high) the-empty-stream (
cons-stream . . . )
(cons-stream 1 (str-enu-int ( low 1) high))
E1 (cons 1 (lambda () (str-enu-int ( low 1)
high)) E1
40Calling stream-cdr
(define s1 (stream-cdr s)) (stream-cdr
s) (stream-cdr (cons 1 (lambda () (str-enu-int 2
3)))) (force (cdr (cons 1 (lambda () (str-enu-int
2 3))))) (force (lambda () (str-enu-int 2
3))) ((lambda () (str-enu-int 2 3))) (str-enu-int
2 3) (cons-stream 2 (str-enu-int 3 3)) (cons 2
(delay (str-enu-int 3 3))) (cons 2 (lambda ()
(str-enu-int 3 3)))
41(define s1 (stream-cdr s)) GE(force (cdr s))
GE
str-enu-int
GE
s
E1
low 1
high 3
plow high b (if (gt low high) the-empty-stream (
cons-stream . . . )
1
p b(str-enu-int ( low 1) high)
42stream-ref, stream-map
(define (stream-ref s n) (if ( n 0)
(stream-car s) (stream-ref (stream-cdr s)
(- n 1))))
(define (stream-map proc s) (if (stream-null?
s) the-empty-stream (cons-stream
(proc (stream-car s))
(stream-map proc (stream-cdr s)))) Also a version
with multiple stream arguments
43Stream-filter
(define (stream-filter pred stream) (cond
((stream-null? stream) the-empty-stream)
((pred (stream-car stream)) (cons-stream
(stream-car stream)
(stream-filter pred
(stream-cdr stream)))) (else
(stream-filter pred (stream-cdr stream)))))
44Applicative vs. Normal order evaluation.
(car (cdr (filter prime? (enu-int 10
1000000))))
(stream-car (stream-cdr (stream-filter
prime?(str-enu-int 10 1000000))))
- Both return the second prime larger or equal to
10 (which is 13) - With lists it takes about 1000000 operations
- With streams about three.
45How does it work? - I
(stream-car (stream-cdr (stream-filter
prime? (str-enu-int 10 1000))))
(stream-car (stream-cdr (stream-filter
prime? (cons 10 (delay (str-enu-int 11 1000))))))
(stream-car (stream-cdr (stream-filter
prime? (force (lambda () (str-enu-int 11
1000))))))
(stream-car (stream-cdr (stream-filter prime?
(cons 11 (delay (str-enu-int 12 1000))))))
46How does it work? - II
(stream-car (stream-cdr (cons 11
(delay (stream-filter prime?
(stream-cdr (cons 11 (delay (str-enu-int 12
1000)))))))))
(stream-car (stream-filter prime?
(stream-cdr (cons 11 (delay (str-enu-int 12
1000))))))
(stream-car (stream-filter prime? (str-enu-int
12 1000)))
47How does it work? - III
(stream-car (stream-filter prime? (cons 12
(delay (str-enu-int 13 1000)))))
(stream-car (stream-filter prime? (str-enu-int
13 1000)))
(stream-car (stream-filter prime? (cons
13 (delay (str-enu-int 14 1000)))))
(stream-car (cons 13 (delay (stream-filter
prime? (stream-cdr (cons 13 (delay
(str-enu-int 14 1000))))))))
13
48Memoization
Suppose we have the following scenario (define
x (delay (very-hard-function a))) (force
x) (force x)
We need to call the hard function twice.
Scheme will automatically detect that this is the
second time we try to evaluate the function and
use the value we have evaluated before.
49How is it done?
delay is actually defined as follows (delay
ltexpgt) translates to (memo-proc (lambda ()
ltexpgt)) (define (memo-proc proc) (let
((already-run? f) (result f))
(lambda () (if (not already-run?)
(begin (set! result (proc))
(set! already-run? true)
result) result))))