Class 26 4 November 2019 Queues and amortized analysis Rackette - - PowerPoint PPT Presentation
Class 26 4 November 2019 Queues and amortized analysis Rackette - - PowerPoint PPT Presentation
Class 26 4 November 2019 Queues and amortized analysis Rackette operations in detail Abstract Data Types Value of abstraction Simplicity given generality Build a stack ADT once, use it in a million places Substitutability
Abstract Data Types
- Value of abstraction
– Simplicity given generality
- Build a stack ADT once, use it in a million places
– Substitutability – Data hiding
Implementation Neutrality
- When we use a module that meets the
module type Stack, we know that we can replace it with any other module that meets that same requirement!
Maintaining Invariants
- Example: In a binary search tree (which
we'll encounter very soon), every value in the left child of a node must be less than the value at the node.
- That's an invariant of the data structure
- If access to the data structure is limited,
we can ensure that all accesses maintain this invariant
Stack review
module ListStack = { type stack('a) = Stack (list('a)); let empty: stack('a) = Stack([]); let isEmpty: stack('a) => bool = s => (s == empty); let push: ('a, stack('a)) => stack('a) = (datum, Stack(lst)) => Stack ([datum,...lst]); let pop : stack('a) => stack('a) = fun | Stack([]) => failwith("Can't pop from empty stack.") | Stack([hd, ...tl]) => Stack(tl); let top: stack('a) => 'a = fun | Stack([]) => failwith("Empty stack has no top element.") | Stack([hd, ...tl]) => hd; };
runtime performance?
Stack summary
Queue review
- Like lining up to get on a bus
- “First in, first out”, or FIFO
ADT
module type Queue = { type queue; let emptyQ; let enq: (int, queue) => queue; let deq: queue => queue; let first: queue => int; };
One implementation: two-list queue
module type Queue = { type queue; let emptyQ; let enq: (int, queue) => queue; let deq: queue => queue; let first: queue => int; }; module TwoListQueue : Queue = { type queue = (list(int), list(int)); let emptyQ = ([], []); let enq: (int, queue) => queue = (num, (front, back)) => (front, [num, ... back]); let rec deq: queue => queue = fun ... };
Alternative implementation
- Our two-list queue:
module TwoListQueue : Queue = { type queue = (list(int), list(int)); let emptyQ = ([], []); let enq: (int, queue) => queue = (num, (front, back)) => (front, [num, ... back]); let rec deq: queue => queue = fun ... };
Alternative implementation
- A two-stack queue.
- pen ListStack;
module TwoStackQueue : Queue = { type queue = (stack(int), stack(int)); let emptyQ = (empty, empty); let enq: (int, queue) => queue = (num, (front, back)) => (front, push(num, back)); let rec deq: queue => queue = fun ... };
Let's back up and do something basic…
Another Implementation of Queue ADT: ListQueue
Does the two-list queue do any better?
- Two lists!
- enq: push things onto list 1 (the "back"
- f the queue). O(n -> 1)
- deq: pop things off of stack 2 (the "front
- f the queue") O(n -> 1) (usually)
- peek: look at stack2’s top element O(n-
>1) (usually)
Analysis Problem/Partial Solution
Amortized analysis
What if you deq twice in a row?
Clever amortized analysis for 2-stack queue
Rackette Examples
- First in hideous detail…
- Then a little faster
- Every question-mark is a point for class
discussion.
(define a 3)
- The concrete program has three "pieces", each of them a list.
- first one: List([Symbol("define"), Symbol("a"), Num(3)])
- Parse converts this to an abstract program piece:
type abstractProgramPiece = | Definition(definition) | Expression(expression);
- Which kind is it? How do you know?
- A "Definition" of course! Starts with the symbol "define"
- Data associated to a Definition:
type definition = (name, expression);
- "name" will be "a", "expression" will be the "3"…
type name = ID(string); type expression = | Num(int) | Bool(bool) | ...
(define a 3)
Definition( (ID("a"), Num(3) )
- We "process" a definition by creating a new TLE containing a binding from the ID to the value of the
expression
- Notice: a binding associates an ID (or "name") to a value
type value = | VNum(int) | VBool(bool) | VList(list(value)) | VBuiltin(string, list(value) => value) | VClosure(list(name), expression, environment) and environment = (list(binding)) and binding = (name, value);
- What we've got --- Num(3) --- is not a value; it's an expression.
- "evaluation" converts expressions to values!
(define a 3)
let addDefinition = fun | (env, (id, expr)) => … let process: abstractProgram => list(value) = pieces => { let rec processHelper: (environment, abstractProgram) => list(value) = (tle, pieces) => switch (pieces) { | [] => [] | [Definition(d), ...tl] => processHelper(addDefinition((tle, d)), tl) | [Expression(e), ...tl] => [ eval((tle, [], e)), ...processHelper(tle, tl), ] }; processHelper(initialTle, pieces); };
After processing (define a 3)
- The (new) TLE contains a binding
Name(ID("a")) -> Vnum(3)
Simplified version
- Skip all parsing steps
- Skip details of adding a binding to an
envt
(define a 3)
- It’s a definition
- 2nd item must be an identifier
– Check!
- 3rd item must be an expression
– Check!
- Evaluate 3rd item
– Value is the number 3
- Add binding to TLE a -> 3.
- More precisely, Name(ID("a")) -> VNum(3)
+ -> addn builtin proc * -> mult builtin proc cons -> cons builtin proc … a -> 3
(+ 3 2)
- It’s a procedure-application expr
- 1st item must be evaluate to a proc
– Check!
- Evaluate other items to get “actuals”
– which are? [What data types?]
- Apply proc to actual args to get value
- Print value (because it’s a top-level
expression)
- "5"
+ -> addn builtin proc * -> mult builtin proc cons -> cons builtin proc …
(let ((a 2)) (+ a 1))
- It’s a let-expression
- 2nd item is a list of ident-exp pairs
- 3rd item must be an expression
- Temporarily extend current envt
with new bindings from 2nd item
- Evaluate 3rd item in extended envt
– Get 3
- Remove temporary bindings
- Note the “lookup from the bottom” rule
+ -> addn builtin proc * -> mult builtin proc cons -> cons builtin proc …
a -> 2