-- Exercise set 5a -- -- * defining algebraic datatypes -- * recursive datatypes module Set5a where import Mooc.Todo ------------------------------------------------------------------------------ -- Ex 1: Define the type Vehicle that has four constructors: Bike, -- Bus, Tram and Train. -- -- The constructors don't need any fields. data Vehicle = Bike | Bus | Tram | Train ------------------------------------------------------------------------------ -- Ex 2: Define the type BusTicket that can represent values like these: -- - SingleTicket -- - MonthlyTicket "January" -- - MonthlyTicket "December" data BusTicket = SingleTicket | MonthlyTicket String ------------------------------------------------------------------------------ -- Ex 3: Here's the definition for a datatype ShoppingEntry that -- represents an entry in a shopping basket. It has an item name (a -- String), an item price (a Double) and a count (an Int). You'll also -- find two examples of ShoppingEntry values. -- -- Implement the functions totalPrice and buyOneMore below. data ShoppingEntry = MkShoppingEntry String Double Int deriving Show threeApples :: ShoppingEntry threeApples = MkShoppingEntry "Apple" 0.5 3 twoBananas :: ShoppingEntry twoBananas = MkShoppingEntry "Banana" 1.1 2 -- totalPrice should return the total price for an entry -- -- Hint: you'll probably need fromIntegral to convert the Int into a -- Double -- -- Examples: -- totalPrice threeApples ==> 1.5 -- totalPrice twoBananas ==> 2.2 totalPrice :: ShoppingEntry -> Double totalPrice (MkShoppingEntry _ price amount) = price * fromIntegral amount -- buyOneMore should increment the count in an entry by one -- -- Example: -- buyOneMore twoBananas ==> MkShoppingEntry "Banana" 1.1 3 buyOneMore :: ShoppingEntry -> ShoppingEntry buyOneMore (MkShoppingEntry name price amount) = MkShoppingEntry name price $ amount+1 ------------------------------------------------------------------------------ -- Ex 4: define a datatype Person, which should contain the age (an -- Int) and the name (a String) of a person. -- -- Also define a Person value fred, and the functions getAge, getName, -- setAge and setName (see below). data Person = Person Int String deriving Show -- fred is a person whose name is Fred and age is 90 fred :: Person fred = Person 90 "Fred" -- getName returns the name of the person getName :: Person -> String getName (Person _ name) = name -- getAge returns the age of the person getAge :: Person -> Int getAge (Person age _) = age -- setName takes a person and returns a new person with the name changed setName :: String -> Person -> Person setName name (Person age _) = Person age name -- setAge does likewise for age setAge :: Int -> Person -> Person setAge age (Person _ name) = Person age name ------------------------------------------------------------------------------ -- Ex 5: define a datatype Position which contains two Int values, x -- and y. Also define the functions below for operating on a Position. -- -- Examples: -- getY (up (up origin)) ==> 2 -- getX (up (right origin)) ==> 1 data Position = Position Int Int -- origin is a Position value with x and y set to 0 origin :: Position origin = Position 0 0 -- getX returns the x of a Position getX :: Position -> Int getX (Position x _) = x -- getY returns the y of a position getY :: Position -> Int getY (Position _ y) = y -- up increases the y value of a position by one up :: Position -> Position up (Position x y) = Position x (y+1) -- right increases the x value of a position by one right :: Position -> Position right (Position x y) = Position (x+1) y ------------------------------------------------------------------------------ -- Ex 6: Here's a datatype that represents a student. A student can -- either be a freshman, a nth year student, or graduated. data Student = Freshman | NthYear Int | Graduated deriving (Show,Eq) -- Implement the function study, which changes a Freshman into a 1st -- year student, a 1st year student into a 2nd year student, and so -- on. A 7th year student gets changed to a graduated student. A -- graduated student stays graduated even if he studies. study :: Student -> Student study s = case s of Freshman -> NthYear 1 NthYear 7 -> Graduated NthYear n -> NthYear (n+1) Graduated -> Graduated ------------------------------------------------------------------------------ -- Ex 7: define a datatype UpDown that represents a counter that can -- either be in increasing or decreasing mode. Also implement the -- functions zero, toggle, tick and get below. -- -- NB! Define _two_ constructors for your datatype (feel free to name the -- constructors however you want) -- -- Examples: -- -- get (tick zero) -- ==> 1 -- get (tick (tick zero)) -- ==> 2 -- get (tick (tick (toggle (tick zero)))) -- ==> -1 data UpDown = Up Int | Down Int -- zero is an increasing counter with value 0 zero :: UpDown zero = Up 0 -- get returns the counter value get :: UpDown -> Int get ud = case ud of Up n -> n Down n -> n -- tick increases an increasing counter by one or decreases a -- decreasing counter by one tick :: UpDown -> UpDown tick ud = case ud of Up n -> Up (n+1) Down n -> Down (n-1) -- toggle changes an increasing counter into a decreasing counter and -- vice versa toggle :: UpDown -> UpDown toggle ud = case ud of Up n -> Down n Down n -> Up n ------------------------------------------------------------------------------ -- Ex 8: you'll find a Color datatype below. It has the three basic -- colours Red, Green and Blue, and two color transformations, Mix and -- Invert. -- -- Mix means the average of the two colors in each rgb channel. -- -- Invert means subtracting all rgb values from 1. -- -- Implement the function rgb :: Color -> [Double] that returns a list -- of length three that represents the rgb value of the given color. -- -- Examples: -- -- rgb Red ==> [1,0,0] -- rgb Green ==> [0,1,0] -- rgb Blue ==> [0,0,1] -- -- rgb (Mix Red Green) ==> [0.5,0.5,0] -- rgb (Mix Red (Mix Red Green)) ==> [0.75,0.25,0] -- rgb (Invert Red) ==> [0,1,1] -- rgb (Invert (Mix Red (Mix Red Green))) ==> [0.25,0.75,1] -- rgb (Mix (Invert Red) (Invert Green)) ==> [0.5,0.5,1] data Color = Red | Green | Blue | Mix Color Color | Invert Color deriving Show rgb :: Color -> [Double] rgb col = case col of Red -> [1,0,0] Green -> [0,1,0] Blue -> [0,0,1] Mix x y -> map (\(i,v) -> (i+v)/2) $ zip (rgb x) (rgb y) Invert x -> map (\v -> 1-v) (rgb x) ------------------------------------------------------------------------------ -- Ex 9: define a parameterized datatype OneOrTwo that contains one or -- two values of the given type. The constructors should be called One and Two. -- -- Examples: -- One True :: OneOrTwo Bool -- Two "cat" "dog" :: OneOrTwo String data OneOrTwo a = One a | Two a a ------------------------------------------------------------------------------ -- Ex 10: define a recursive datatype KeyVals for storing a set of -- key-value pairs. There should be two constructors: Empty and Pair. -- -- Empty represents an empty collection. It should have no fields. -- -- Pair should have three fields, one for the key, one for the value, -- and one for the rest of the collection (of type KeyVals) -- -- The KeyVals datatype is parameterized by the key type k and -- the value type v. -- -- For example: -- -- Pair "cat" True (Pair "dog" False Empty) :: KeyVals String Bool -- -- Also define the functions toList and fromList that convert between -- KeyVals and lists of pairs. data KeyVals k v = Empty | Pair k v (KeyVals k v) deriving Show toList :: KeyVals k v -> [(k,v)] toList (Pair k v kvList) = (k, v) : (toList kvList) toList Empty = [] fromList :: [(k,v)] -> KeyVals k v fromList ((k,v):xs) = Pair k v (fromList xs) fromList _ = Empty ------------------------------------------------------------------------------ -- Ex 11: The data type Nat is the so called Peano -- representation for natural numbers. Define functions fromNat and -- toNat that convert natural numbers to Ints and vice versa. -- -- Examples: -- fromNat (PlusOne (PlusOne (PlusOne Zero))) ==> 3 -- toNat 3 ==> Just (PlusOne (PlusOne (PlusOne Zero))) -- toNat (-3) ==> Nothing -- data Nat = Zero | PlusOne Nat deriving (Show,Eq) fromNat :: Nat -> Int fromNat n = case n of PlusOne n -> 1 + fromNat n Zero -> 0 toNat :: Int -> Maybe Nat toNat z = if z >= 0 then Just (toNat' z) else Nothing where toNat' :: Int -> Nat toNat' z | z>0 = PlusOne $ toNat' (z-1) | z==0 = Zero ------------------------------------------------------------------------------ -- Ex 12: While pleasingly simple in its definition, the Nat datatype is not -- very efficient computationally. Instead of the unary Peano natural numbers, -- computers use binary numbers. -- -- Binary numbers are like decimal numbers, except that binary numbers have -- only two digits (called bits), 0 and 1. The table below gives some -- examples: -- -- decimal | binary -- --------+------- -- 0 | 0 -- 1 | 1 -- 2 | 10 -- 7 | 111 -- 44 | 101100 -- -- For allowing arbitrarily long binary numbers, our representation, the -- datatype Bin, includes a special End constructor for denoting the end of -- the binary number. In order to make computation with Bin easier, the bits -- are represented in increasing order by significance (i.e. "backwards"). -- Consider the Bin numbers O (I (I End)), representing 110 in binary or -- 6 in decimal, and I (I (O End)) that represents 011 in binary or 3 in -- decimal. The most significant (last) bit, the bit I, of O (I (I End)) is -- greater than the bit O, which is the most significant bit of I (I (O End)). -- Therefore, O (I (I End)) is greater than I (I (O End)). -- -- Your task is to write functions prettyPrint, fromBin, and toBin that -- convert Bin to human-readable string, Bin to Int, and Int to Bin -- respectively. -- -- Examples: -- prettyPrint End ==> "" -- prettyPrint (O End) ==> "0" -- prettyPrint (I End) ==> "1" -- prettyPrint (O (O (I (O (I End))))) ==> "10100" -- map fromBin [O End, I End, O (I End), I (I End), O (O (I End)), -- I (O (I End))] -- ==> [0, 1, 2, 3, 4, 5] -- fromBin (I (I (O (O (I (O (I (O End)))))))) ==> 83 -- fromBin (I (I (O (O (I (O (I End))))))) ==> 83 -- map toBin [0..5] ==> -- [O End,I End,O (I End),I (I End),O (O (I End)),I (O (I End))] -- toBin 57 ==> I (O (O (I (I (I End))))) -- -- Challenge: Can you implement toBin by directly converting its input into a -- sequence of bits instead of repeatedly applying inc? -- data Bin = End | O Bin | I Bin deriving (Show, Eq) -- This function increments a binary number by one. inc :: Bin -> Bin inc End = I End inc (O b) = I b inc (I b) = O (inc b) prettyPrint :: Bin -> String prettyPrint (O b) = prettyPrint b ++ "0" prettyPrint (I b) = prettyPrint b ++ "1" prettyPrint End = "" fromBin :: Bin -> Int fromBin b = fromBin' b 0 fromBin' (I b) acc = fromBin' b (acc+1) + 2^acc fromBin' (O b) acc = fromBin' b (acc+1) fromBin' End _ = 0 toBin :: Int -> Bin toBin n = toBin' n End toBin' :: Int -> Bin -> Bin toBin' 0 b = O End toBin' n b = if fromBin b >= n then b else toBin' n (inc b)