(* 15-212, Spring 2011 *) (* Michael Erdmann *) (* Code for Lecture 7: Data Structures, Abstraction Functions, and Representation Invariants *) (************************************************************************) (* Queue data structure *) signature QUEUE = sig type 'a queue (* abstract type *) val empty : 'a queue val enq : 'a queue * 'a -> 'a queue val null : 'a queue -> bool exception Empty (* deq (q) raises Empty if q is empty *) val deq : 'a queue -> 'a * 'a queue end; (* Queue1 gives an inefficient implementation *) (* try also with structure Queue1 : QUEUE to expose the implementation for value printing Abstraction function: A queue is implemented as a list consisting of the queue elements in arrival order. Representation Invariants: none *) structure Queue1 :> QUEUE = struct type 'a queue = 'a list val empty = nil fun enq (q,x) = q @ [x] fun null (nil) = true | null ( _ ) = false exception Empty (* deq (q) raises Empty if q is empty *) fun deq (x::q) = (x,q) | deq (nil) = raise Empty end; val queue0 : int Queue1.queue = Queue1.empty; val queue1 : int Queue1.queue = Queue1.enq(queue0, 1); val queue2 : int Queue1.queue = Queue1.enq(queue1, 2); val queue2': int Queue1.queue = Queue1.enq(queue1, 2000); val queue3 = Queue1.deq(queue2); val queue3' = Queue1.deq(queue2'); (* Queue2 gives a more efficient implementation *) (* try also with structure Queue2 : QUEUE to expose the implementation for value printing Abstraction function: A queue is implemented as a pair of lists (front, back) such that front @ (rev back) consists of the queue elements in arrival order. Representation Invariants: none *) structure Queue2 :> QUEUE = struct type 'a queue = 'a list * 'a list val empty = (nil, nil) fun enq ((front, back), x) = (front, x::back) fun null (nil,nil) = true | null _ = false exception Empty (* deq (q) raises Empty if q is empty *) fun deq (x::front, back) = (x, (front, back)) | deq (nil, nil) = raise Empty | deq (nil, back) = deq(rev back, nil) end; (* An abbreviation for the structure name *) (* Try also: structure Q = Queue1 *) structure Q = Queue2; val q0 : int Q.queue = Q.empty; val q1 = Q.enq (q0, 1); val q2 = Q.enq (q1, 2); val q3 = Q.enq (q2, 3); val (x1, q4) = Q.deq (q3); val q4' = Q.enq (q4, 4); val q5 = Q.enq (q4', 5); val (x2, q6) = Q.deq (q5); (* Previous queues are still accessible *) val (x1', q4'') = Q.deq (q3); (* We can shadow previous queues for garbage collection *) (* Only the last one is accessible, others are shadowed *) val q = Q.empty; val q = Q.enq (q, "a"); val q = Q.enq (q, "b"); val q = Q.enq (q, "c"); val (s1, q) = Q.deq (q); val (s2, q) = Q.deq (q); (* Equality for queues *) (* Elements must permit equality test *) (* val eq : ''a Q.queue * ''a Q.queue -> bool *) (* Inside Queue1 we could write: *) (* fun eq (q1,q2) = (q1 = q2) *) (* Inside Queue2 we could write *) (* fun eq ((front1,back1), (front2,back2)) = (front1 @ rev(back1) = front2 @ rev(back2)) *) (* Using the abstract types we write *) fun eq (q1, q2) = if Q.null q1 then Q.null q2 else if Q.null q2 then false else let val (x1, q1') = Q.deq q1 val (x2, q2') = Q.deq q2 in x1 = x2 andalso eq(q1',q2') end; (* Using mutual recursion *) (* val eq : ''a Q.queue * ''a Q.queue -> bool *) (* and eq' : (''a * ''a Q.queue) * (''a * ''a Q.queue) -> bool *) fun eq (q1, q2) = if Q.null q1 then Q.null q2 else if Q.null q2 then false else eq' (Q.deq q1, Q.deq q2) and eq' ((x1, q1), (x2, q2)) = x1 = x2 andalso eq (q1, q2); (* Queues of optional integers *) datatype 'a option = NONE | SOME of 'a; type intOptQueue = int option Q.queue; (* Queues of integers or reals *) datatype number = Int of int | Real of real; type numberQueue = number Q.queue; (************************************************************************) (* Signature for dictionaries *) (* For simplicity, we assume keys are strings, while stored entries are of arbitrary type. This is prescribed in the signature. Existing entries may be "updated" by inserting a new entry with the same key. *) signature DICT = sig type key = string (* concrete type *) type 'a entry = key * 'a (* concrete type *) type 'a dict (* abstract type *) val empty : 'a dict val lookup : 'a dict -> key -> 'a option val insert : 'a dict * 'a entry -> 'a dict end; (* signature DICT *) (************************************************************************) (* Association list implementation of dictionaries popular in Lisp *) structure AssocList :> DICT = struct type key = string type 'a entry = key * 'a type 'a dict = ('a entry) list (* Abstraction function: A dictionary is implemented as a list of entries. Representation Invariant: If there are multiple entries with the same key, only the leftmost entry is valid. *) val empty = nil fun lookup l key = (* lk l ==> leftmost entry in l matching key or NONE *) let fun lk nil = NONE | lk ((key1,datum1)::l) = if (key = key1) then SOME(datum1) else lk l in lk l end (* Inserting shadows previous entries with same key *) fun insert (l, entry) = entry::l end; (* structure AssocList *) (************************************************************************) (* Straight binary search tree implementation of dictionaries *) structure BinarySearchTree :> DICT = struct type key = string type 'a entry = string * 'a datatype 'a tree = Empty | Node of 'a entry * 'a tree * 'a tree type 'a dict = 'a tree (* Abstraction function: A dictionary is implemented as a binary tree whose nodes hold the entries. Representation Invariant: The tree is ordered: For every node Node((key1,datum1), left, right), every key in left is LESS than key1, and every key in right is GREATER than key1 *) val empty = Empty fun lookup tree key = let fun lk (Empty) = NONE | lk (Node((key1,datum1), left, right)) = (case String.compare(key,key1) of EQUAL => SOME(datum1) | LESS => lk left | GREATER => lk right) in lk tree end fun insert (tree, entry as (key,datum)) = (* val ins : 'a dict -> 'a dict *) (* ins (tree) inserts entry into tree *) let fun ins (Empty) = Node(entry, Empty, Empty) | ins (Node(entry1 as (key1,datum1), left, right)) = (case String.compare(key,key1) of EQUAL => Node(entry, left, right) | LESS => Node(entry1, ins left, right) | GREATER => Node(entry1, left, ins right)) in ins tree end end; (* structure BinarySearchTree *) (* Some Examples *) structure BST = BinarySearchTree; val d0 = BST.empty; val d1 = BST.insert (d0, ("a",1)); val d2 = BST.insert (d1, ("b",2)); val d3 = BST.insert (d2, ("c",3)); val d4 = BST.insert (d3, ("d",4)); val result = BST.lookup d4 "e"; val result = BST.lookup d4 "a"; (* or, using the curried form of lookup: *) val look4 = BST.lookup d4; val result = look4 "e"; val result = look4 "a"; (************************************************************************) (* Next Lecture: Red/Black Tree implementation of DICT. *) (************************************************************************)