Topic: Finish Motifs, Trees Date: Oct. 9, 2009 Number: 9 Examples: Trees.hs Reading: Chap. 7 ---- Most of this class finished up motifs - see lecture notes for Lecture 8. ----- We have looked at lists, and will continue to use them. But there are times when we need a tree-like structure, either because the data is naturally tree-like (organization charts, book/chapter/section/paragraph structure, etc.) or in order to do fast lookup in a dictionary or map. Trees are recursive structures. Well, lists are, also: data MyList a = Nil | MakeList a (MyList a) (note that "a" stands for a type, so you are allowed to create a MyList with any type of data; e.g. "MyList Int" or "MyList String".) In fact, Haskell has a built-in definition like this, except that instead of Nil it uses [] and instead of MakeList it uses (:). But trees have higher branching. We will consider binary trees. Turns out that you can represent general trees using binary tree structure: (leftmost child, right sibling) nodes. So book defines several varieties of trees: All data at leaves. Nothing internal. (Will see this in PS 3, when we do hierarchical clustering.) data Tree a = Leaf a | Branch (Tree a) (Tree a) All data at internal nodes. (Will see this in BSTMap, binary search trees used to access data quickly. Will talk about maps next class.) data InternalTree a = ILeaf | IBranch a (InternalTree a) (InternalTree a) Or even both. Note that the internal data and the external data may have different types. data FancyTree a b = FLeaf a | FBranch b (FancyTree a b) (FancyTree a b) So what sorts of things can we do with trees? Lots of the same things that we can do on lists. Consider: mapTree :: (a->b) -> Tree a -> Tree b Want to replace every data value by a function of that value. mapTree f (Leaf x) = Leaf (f x) mapTree f (Branch t1 t2) = Branch (mapTree f t1) (mapTree f t2) A second choice might be called "toList". Converts the data in the leaves to a list in the same order, read left to right. fringe :: Tree a -> [a] fringe (Leaf x) = [x] fringe (Branch t1 t2) = fringe t1 ++ fringe t2 Is this efficient? Not very - lots of concatenating going on. Will see a more efficient approach later. The equivalent of List length is size, and it is similar: treeSize :: Tree a -> Integer treeSize (Leaf x) = 1 treeSize (Branch t1 t2) = treeSize t1 + treeSize t2 This counts the number of leaves. Will count internal nodes later. Can also do height: treeHeight :: Tree a -> Integer treeHeight (Leaf x) = 0 treeHeight (Branch t1 t2) = 1 + max (treeHeight t1) (treeHeight t2) But we can also use trees to represent other things. One is an expression tree. Used to represent arithmetical expressions: data Expr = C Float | Expr :+ Expr | Expr :- Expr | Expr :* Expr | Expr :/ Expr deriving Show Here the ":+", etc. are "infix constructors". Normally the constructors begin with a capital letter, to distinguish them from normal function names. Hard to capitalize a "+", though! So by putting a ":" in front of the operator, it makes it a constuctor. (Note that ":" for constructing lists follows the same convention! It is an operator starting with a colon.) So C is a constant, the rest are arithmetical operators. So can represent: (9*5 - 36/3)*(2 + 4) by: expr = ((C 9 :* C 5) :- (C 36 :/ C 3)) :* (C 2 :+ C 4) Why bother? It gives the order and nesting of operations. No associativity questions. And makes it very easy to write an evaluator: evaluate :: Expr -> Float evaluate (C x) = x evaluate (e1 :+ e2) = evaluate e1 + evaluate e2 evaluate (e1 :- e2) = evaluate e1 - evaluate e2 evaluate (e1 :* e2) = evaluate e1 * evaluate e2 evaluate (e1 :/ e2) = evaluate e1 / evaluate e2 Value of a constant is the value, and for the other operators we do the operation on recursively evaluated expressions! Hard to imagine an easier or shorter evaluator. Demo that it works.