{- CS380 Assignment 2
Name:
College email:
Resources / collaborators:
**DUE ON GRADESCOPE BEFORE CLASS ON WEDNESDAY, FEBRUARY 8, 2017.**
-}
{-# LANGUAGE GADTSyntax #-}
module Hw02 where
import Test.HUnit
--------------------------------------------------------------------------------
-- Binary Search Trees
{- In this assignment, you will be writing a binary search tree implementation
of a finite map. A finite map is a data structure that associates *keys*
(of one type) with *values* (of, potentially, another type). This is useful for,
say, storing an address book or counting word frequencies in a file. Java's
finite map interface is Map (https://docs.oracle.com/javase/8/docs/api/java/util/Map.html);
Python calls this type a dictionary.
Note that this is a *different*, unrelated use of the word "map" than the
higher-order function that we've seen.
To keep things simple, our map will use Strings as its *key* type.
The map will be implemented using a binary search tree.
https://en.wikipedia.org/wiki/Binary_search_tree
The tree type will store a String and a value (of type `a`) at each interior
node. It will obey the usual binary search tree properties. Given a interior
node T with left child L and right child R:
1. The key stored in every node in the tree rooted at L is less than T's key.
2. The key stored in every node in the tree rooted at R is greater than T's key.
By "less" and "greater" here, I mean by using the operators (<) and (>), which
work on Strings.
**** UNIT TESTS ****
Each function must be tested against at least 4 test cases. Add to mine!
-}
-- The tree datatype:
data Tree a where
Leaf :: Tree a
Node :: String -- key
-> a -- value
-> Tree a -- left child
-> Tree a -- right child
-> Tree a
deriving (Eq, Show) -- this allows (==) on trees and (show :: Tree a -> String)
sampleTree1 :: Tree Int
sampleTree1 = Node "pickle" 5
(Node "avocado" 2
Leaf (Node "clementine" 10
Leaf Leaf)) (Node "tomato" 7
(Node "radish" 9
Leaf Leaf) (Node "yam" 1
Leaf Leaf))
sampleTree2 :: Tree Char
sampleTree2 = Node "Haskell" 'x'
(Node "C" 'q'
(Node "Ada" 'p'
Leaf Leaf) (Node "C++" 'r'
Leaf (Node "F#" 'e'
Leaf Leaf))) (Node "OCaml" 'd'
Leaf Leaf)
--------------------------------------------------------------------------------
-- Problem 1
{- Write a function that gets the size (number of interior nodes) of a tree. -}
sizeTree :: Tree a -> Int
sizeTree = error "unimplemented
sizeTree_tests = "sizeTree" ~:
TestList [ "sampleTree1" ~: sizeTree sampleTree1 ~?= 6
, "sampleTree2" ~: sizeTree sampleTree2 ~?= 6 ]
--------------------------------------------------------------------------------
-- Problem 2
{- Write a function that finds a key in a binary search tree and
returns the associated value, if there is one. -}
findTree :: Tree a -> String -> Maybe a
findTree = error "unimplemented"
findTree_tests = "findTree" ~:
TestList [ "pickle" ~: findTree sampleTree1 "pickle" ~?= Just 5
, "Java" ~: findTree sampleTree2 "Java" ~?= Nothing ]
-- Add more tests
--------------------------------------------------------------------------------
-- Problem 3
{- Write a function that inserts a key into a binary search tree. If the key
is already in the tree, then update the value associated with the key. -}
insertTree :: Tree a -> String -> a -> Tree a
insertTree = error "unimplemented"
insertTree_tests
= "insertTree" ~:
TestList [ "insert/find" ~: findTree (insertTree Leaf "hi" "there") "hi" ~?= Just "there"
, "update" ~: findTree (insertTree sampleTree1 "clementine" (-5)) "clementine"
~?= Just (-5) ]
-- Add more tests
--------------------------------------------------------------------------------
-- Problem 4
{- Write a function that maps a function over all the *values* in your tree. -}
mapTree :: (a -> b) -> Tree a -> Tree b
mapTree = error "unimplemented"
mapTree_tests
= "mapTree" ~:
TestList [ "isVowel" ~: findTree (mapTree (`elem` "aeiou") sampleTree2) "F#" ~?= Just True
, "samesize" ~: sizeTree (mapTree (+1) sampleTree1) ~?= 6 ]
-- Add more tests
--------------------------------------------------------------------------------
-- Problem 5
{- Write a function that returns all the key/value pairs of a tree in preorder.
That is, return the key/value pair of a node before recurring into its children. -}
preorder :: Tree a -> [(String, a)]
preorder = error "unimplemented"
preorder_tests
= "preorder" ~:
TestList [ "sampleTree1" ~: preorder sampleTree1 ~?= [ ("pickle", 5), ("avocado",2)
, ("clementine", 10), ("tomato", 7)
, ("radish", 9), ("yam", 1) ]
, "empty" ~: preorder (Leaf :: Tree Integer) ~?= [] ]
-- HUnit struggles if it doesn't know the type
-- of data stored in a polymophic structure
-- Add more tests
--------------------------------------------------------------------------------
-- Problem 6
{- Write a function that uses your tree structure to efficiently compute the
frequencies of words in a list of words. The input to your function is a list
of words (Strings). The output is an association list associating each word
with the number of times it occurs in the list. -}
frequencies :: [String] -> [(String, Int)]
frequencies = error "unimplemented"
frequencies_test
= "frequencies" ~:
TestList [ "palindrome a " ~: lookup "a" (frequencies words) ~?= Just 3
, "palindrome plan" ~: lookup "plan" (frequencies words) ~?= Just 1 ]
where
words = ["a", "man", "a", "plan", "a", "canal", "Panama"]