Deleting from binary search trees
The data structure below implements a "dictionary" abstract data type using a binary search tree. A dictionary maps a string key to some value. In the binary search tree implementation, the search key is a string, and the datum is parametric. Each key-value pair is stored in a tree node, and the tree is ordered by the natural ordering of strings.
Insertion, lookup, and deletion use the same algorithm as a binary search tree with integer keys. This holds for any type with a comparison function.
We'll focus on the deletion operation. A convenient way to delete a node from a binary search tree is to remove the greatest element of the left subtree of the node, and "overwrite" the deleted node with this greatest element. If no greatest element exists, then there is no left subtree, and hence one can simply replace the deleted node with the right subtree.
This operation is straightforward and minimizes changes to the tree. The greatest element will always be a leaf node, and so deleting it requires only changing its parent. The only change to the structure of the tree will be the removal of the greatest element from the left subtree.
structure AssocTree = struct type key = string (* Invariant: for Nodes, data to the left have keys that * are LESS than the datum and the keys of * the data to the right. *) datatype 'a dict = Empty | Node of {key: key,datum: 'a, left: 'a dict,right: 'a dict} fun make():'a dict = Empty fun insert (d:'a dict) (k:key) (x:'a) : 'a dict = case d of Empty => Node{key=k, datum=x, left=Empty, right=Empty} | Node {key=k', datum=x', left=l, right=r} => (case String.compare(k,k') of EQUAL => Node{key=k, datum=x, left=l, right=r} | LESS => Node{key=k',datum=x',left=insert l k x, right=r} | GREATER => Node{key=k',datum=x',left=l, right=insert r k x}) exception NotFound fun lookup (d:'a dict) (k:key) : 'a = case d of Empty => raise NotFound | Node{key=k',datum=x, left=l, right=r} => (case String.compare(k,k') of EQUAL => x | LESS => lookup l k | GREATER => lookup r k) fun delete (d:'a dict) (k: key) : 'a dict = let (* This code can be simplified if we guarantee that * removeGreatest is always called on a non-empty subtree. *) fun removeGreatest(d:'a dict) : ('a dict * ((key * 'a) option)) = case d of Empty => (Empty, NONE) | Node{key=k',datum=x, left=l, right=Empty} => (l, SOME(k', x)) | Node{key=k',datum=x, left=l, right=r} => let val (subtree, greatest) = removeGreatest(r) in (Node{key=k', datum=x, left=l, right=subtree}, greatest) end in case d of Empty => raise NotFound | Node{key=k',datum=x, left=l, right=r} => (case String.compare(k,k') of EQUAL => let val (lefttree, greatest) = removeGreatest(l) in case greatest of NONE => r | SOME(nk,nv) => Node{key=nk, datum=nv, left=lefttree, right=r} end | LESS => Node{key=k', datum=x, left=delete l k, right=r} | GREATER => Node{key=k', datum=x, left=l, right=delete r k}) end end
Evaluating boolean ASTs
Here is a function for evaluating an abstract syntax tree for boolean expressions. Notice how closely the grammar corresponds with the bool_ast datatype.
Grammar
e ::= true | false | e0 andalso e1 | e0 orelse e1
datatype bool_ast = TRUE | FALSE | ANDALSO of bool_ast * bool_ast | ORELSE of bool_ast * bool_ast fun evaluate(exp: bool_ast) : bool = case exp of TRUE => true | FALSE => false | ANDALSO(x,y) => if evaluate(x) then evaluate(y) else false | ORELSE(x,y) => if evaluate(x) then true else evaluate(y)
This evaluation procedure implements short circuit evaluation, as expressions are evaluated only if it is needed.
Suppose we wish to extend the grammar with variables. We'll denote this by id (identifier). In the homework assignment, variables are implemented with substitution. Here, we'll implement variables by making dictionary lookups into a set of bindings:
New grammar
e ::= true | false | e0 andalso e1 | e0 orelse e1 | id
datatype bool_ast = TRUE | FALSE | ANDALSO of bool_ast * bool_ast | ORELSE of bool_ast * bool_ast | ID of string fun evaluate(exp: bool_ast, bindings: bool AssocTree.dict) : bool = case exp of TRUE => true | FALSE => false | ANDALSO(x,y) => if evaluate(x, bindings) then evaluate(y, bindings) else false | ORELSE(x,y) => if evaluate(x, bindings) then true else evaluate(y, bindings) | ID(varname) => AssocTree.lookup bindings varname