Topic: Binary Search Trees, Set ADT, Priority Queue, more ADTs Date: Oct. 14, 2009 Number: 11 Examples: ListPriorityQueue.hs, PQDriver.hs, BSTMap.hs Reading: Chap. 23 PS 3 due a week from next Wednesday, but START NOW SA 6 due Friday. Midterm two weeks from Thursday, and PS 4 out next Wednesday. -- Priority Queue: operations are: -- Return an empty PQ empty :: PriorityQueue a b -- Inserts new item into PQ insert :: (Ord a) => (a, b) -> PriorityQueue a b -> PriorityQueue a b -- Returns the minimum item in PQ getMin :: PriorityQueue a b -> (a, b) -- Deletes the minimum item from PQ deleteMin :: PriorityQueue a b -> PriorityQueue a b -- Returns true if empty isEmpty :: PriorityQueue a b -> Bool ListPriorityQueue.hs implements this Priority Queue ADT. It keeps the list of (priority, value) pairs sorted by priority. That makes it easy to implement getMin and deleteMin. (The minimum priority item is the first thing in the list.) The insert method is similar to the delete method in Map; it runs down the list until it finds something that it is <= to, and inserts there. (Look at code.) Your next short assignment asks you to modify this to use a Map instead of a List to solve the problem. Data.Map has a findMin function that is very convenient for solving this problem. -- Set ADT: use Data.Set documented at: http://www.haskell.org/ghc/docs/latest/html/libraries/containers/ Data-Set.html Important operations: -- Is the set empty? null :: Set a -> Bool size :: Set a -> Int member :: Ord a => a -> Set a -> Bool -- return an empty set empty :: Set a insert :: Ord a => a -> Set a -> Set a delete :: Ord a => a -> Set a -> Set a toList :: Set a -> [a] fromList :: Ord a => [a] -> Set a -- BSTMap Now that we have seen one way to implement a map, we can look at another - using a BST. Show BST tree can be viewed as generalization of binary search. Start with list, build BST tree by binary search visits. Show how to search: at each branch, compare to key. If smaller go left, if larger go right, if equal return the value stored there. If get to leaf, failed. Show how to insert: Same as search, but if find replace or combine with previous key. If get to a leaf, add new element there. Discuss delete, show how to do that. Easy if either child is a Leaf - point parent at the other child (which may also be a leaf). Preserves order. If two children, have a problem. Can't point parent at both. Re-inserting one subtree leads to unbalanced trees. Solution - replace thing with two children by immediate predecessor (or successor) in the list. (Get same answer as original when searching for any item in the tree.) Immediate predecessor is the biggest thing in the left subtree. Find biggest by going right until right child is a Leaf. But that means that this node has a leaf as a child, so can be easily deleted. Then to code. Data type: data Map k a = Leaf | Branch {key :: k, value :: a, left :: (Map k a), right :: (Map k a)} deriving Show Note the field labels. Equivalent to data Map k a = Leaf | Branch k a (Map k a) (Map k a) But if mp is a map, can say (key mp) or (left mp) .... See p. 216. Also, if you want to use these, you have to export them in the module declaration: module BSTMap (Map(Leaf, Branch, key, value, left, right), ... In fact, we export none of these, because we don't want people accessing the data structure except through the methods. Data Hiding. Important. Various functions implemented as BST tree: Empty tree just a leaf: empty :: Map a b empty = Leaf First look at search in lookup: -- Find the key and return the value, or Nothing if not present lookup :: Ord a => a -> Map a b -> Maybe b lookup k Leaf = Nothing lookup k (Branch k1 v l r) | k == k1 = Just v | k > k1 = lookup k r | k < k1 = lookup k l Note three choices for branch: go left, go right, or return Just v. How do we fail? Reach a leaf. Return Nothing. Given this, (!) and member are exactly the same as in ListMap. Insertion is fairly similar to list insertion, in its cases. You create a singleton tree if you are at a Leaf, you replace the node with the computed value if the keys are equal, and you call yourself recusively on ONE branch otherwise. -- Insert key k and value v into this Map. -- Replace value with (f new_val old_val) if key already present. insertWith :: Ord a => (b -> b -> b) -> a -> b -> Map a b -> Map a b insertWith f k v Leaf = Branch k v Leaf Leaf insertWith f k v (Branch k1 v1 l r) | k == k1 = Branch k (f v v1) l r -- Replace value using f | k > k1 = Branch k1 v1 l (insertWith f k v r) | k < k1 = Branch k1 v1 (insertWith f k v l) r Delete is trickier. Note the case. If one of your children is a leaf it is easy - return the other child. Otherwise you get the maximum pair in the left subtree, replace the node to be deleted by that, and then delete that node in the left subtree. (It can only have one child.) -- Delete key from Map. delete :: Ord a => a -> Map a b -> Map a b delete k Leaf = Leaf delete k (Branch k1 v l r) | k > k1 = Branch k1 v l (delete k r) | k < k1 = Branch k1 v (delete k l) r | k == k1 = case (l, r) of (Leaf, _) -> r (_, Leaf) -> l (_, _) -> let (k2, v2) = maxPair l in Branch k2 v2 (delete k2 l) r To find the max, go right until you find a leaf. -- Finds the pair with the maximum key. -- Only works on non-empty trees. maxPair :: Map a b -> (a, b) maxPair (Branch k v _ Leaf) = (k, v) maxPair (Branch _ _ _ r ) = maxPair r I promised you a more efficient "fringe". Here it is toList. It uses an accumulator acc, which holds everything that comes after a current node that is not in its right subtree. -- Converts a tree into a list of (key, value) pairs toList :: Map a b -> [(a,b)] toList tree = toListHelper tree [] where toListHelper :: Map a b -> [(a,b)] -> [(a,b)] toListHelper Leaf acc = acc toListHelper (Branch k v l r) acc = toListHelper l ((k,v) : toListHelper r acc) This fromList should seem familiar by now, except for this funny "uncurry". It allows a function expecting two arguments to accept an ordered pair instead. -- Converts a list of (key, value) pairs to a Map fromList :: (Ord a) => [(a,b)] -> Map a b fromList = foldr (uncurry insert) empty The others are fairly straightforward: -- Returns a list of the keys in the Map keys :: Ord a => Map a b -> [a] keys = map fst . toList -- Returns the number of keys in theMap size :: Map a b -> Int size Leaf = 0 size (Branch _ _ l r) = 1 + size l + size r showTree is kind of interesting. It allows you to use indentation to show structure. Each level down you increase the indent amount. -- Prints the tree, using indentation to show depth showTree :: (Show a, Show b) => Map a b -> IO () showTree t = prettyPrint t "" -- Helper function for showTree prettyPrint :: (Show a, Show b) => Map a b -> String -> IO () prettyPrint Leaf indent = return () prettyPrint (Branch k v l r) indent = do prettyPrint r (indent ++ " ") putStr (indent ++ (show k) ++ " " ++ (show v) ++ "\n") prettyPrint l (indent ++ " ")