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 – 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 runtime performance? | Stack([]) => failwith("Empty stack has no top element.") | Stack([hd, ...tl]) => hd; };
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. open 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" of the queue). O(n -> 1) • deq: pop things off of stack 2 (the "front of 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 + -> addn builtin proc * -> mult builtin proc • 2 nd item must be an identifier cons -> cons builtin proc – Check! … a -> 3 • 3 rd item must be an expression – Check! • Evaluate 3 rd item – Value is the number 3 • Add binding to TLE a -> 3. • More precisely, Name(ID("a")) -> VNum(3)
(+ 3 2) + -> addn builtin proc * -> mult builtin proc • It’s a procedure-application expr cons -> cons builtin proc … • 1 st 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"
(let ((a 2)) (+ a 1)) + -> addn builtin proc * -> mult builtin proc cons -> cons builtin proc • It’s a let-expression … a -> 2 • 2 nd item is a list of ident-exp pairs • 3 rd item must be an expression • Temporarily extend current envt with new bindings from 2 nd item • Evaluate 3 rd item in extended envt – Get 3 • Remove temporary bindings • Note the “lookup from the bottom” rule
Recommend
More recommend