|
| 1 | +{-# LANGUAGE ParallelListComp #-} |
| 2 | +{-# OPTIONS_GHC -fwarn-incomplete-patterns #-} |
| 3 | +module Week04Solutions where |
| 4 | + |
| 5 | +import Prelude hiding (foldr, foldl, Maybe (..), Left, Right, filter, zip, map, concat) |
| 6 | +import Data.List.Split (splitOn) |
| 7 | +import Data.List hiding (foldr, foldl, filter, map, concat) |
| 8 | +import Week04 |
| 9 | + |
| 10 | +{------------------------------------------------------------------------------} |
| 11 | +{- TUTORIAL QUESTIONS -} |
| 12 | +{------------------------------------------------------------------------------} |
| 13 | + |
| 14 | +{- 1. The following recursive function returns the list it is given as |
| 15 | + input: -} |
| 16 | + |
| 17 | +listIdentity :: [a] -> [a] |
| 18 | +listIdentity [] = [] |
| 19 | +listIdentity (x:xs) = x : listIdentity xs |
| 20 | + |
| 21 | +{- Write this function as a 'foldr': -} |
| 22 | + |
| 23 | +listIdentity' :: [a] -> [a] |
| 24 | +listIdentity' = foldr (\x r -> x : r) -- step case, combines the head and the tail using ':' |
| 25 | + [] -- base case, the empty list [] |
| 26 | + |
| 27 | +{- See how the base case is the same as the first clause in the original |
| 28 | + definition of 'listIdentity'. The step case is the same as the |
| 29 | + second clause, except that the recursive call 'listIdentity xs' has |
| 30 | + been replaced by 'r'. |
| 31 | +
|
| 32 | + We can also shorten this to: |
| 33 | +
|
| 34 | + listIdentity' = foldr (:) [] |
| 35 | +
|
| 36 | + because '(:)' is the same thing as '(\x r -> x : r)': any infix |
| 37 | + operation, like ':' can be written as a function that takes two |
| 38 | + arguments by putting it in brackets. |
| 39 | +
|
| 40 | + Let's see how this works by writing out the steps on a short list: |
| 41 | +
|
| 42 | + foldr (\x r -> x:r) [] [1,2] |
| 43 | + = (\x r -> x:r) 1 (foldr (\x r -> x:r) [] [2]) |
| 44 | + = (\x r -> x:r) 1 ((\x r -> x:r) 2 (foldr (\x r -> x:r) [] [])) |
| 45 | + = (\x r -> x:r) 1 ((\x r -> x:r) 2 []) |
| 46 | + = (\x r -> x:r) 1 (2:[]) |
| 47 | + = 1:2:[] |
| 48 | + = [1,2] |
| 49 | +-} |
| 50 | + |
| 51 | +{- 2. The following recursive function does a map and a filter at the |
| 52 | + same time. If the function argument sends an element to |
| 53 | + 'Nothing' it is discarded, and if it sends it to 'Just b' then |
| 54 | + 'b' is placed in the output list. -} |
| 55 | + |
| 56 | +mapFilter :: (a -> Maybe b) -> [a] -> [b] |
| 57 | +mapFilter f [] = [] |
| 58 | +mapFilter f (x:xs) = case f x of |
| 59 | + Nothing -> mapFilter f xs |
| 60 | + Just b -> b : mapFilter f xs |
| 61 | + |
| 62 | +{- Write this function as a 'foldr': -} |
| 63 | + |
| 64 | +mapFilter' :: (a -> Maybe b) -> [a] -> [b] |
| 65 | +mapFilter' f = foldr (\x r -> case f x of Nothing -> r -- \___ step case |
| 66 | + Just b -> b : r) -- / |
| 67 | + [] -- base case |
| 68 | + |
| 69 | +{- The base case is the same as for 'listIdentity'' above. |
| 70 | +
|
| 71 | + In the step case, we have to decide whether or not to add the new |
| 72 | + element to the list. We 'case' on the result of 'f x'. If it is |
| 73 | + 'Nothing', we return 'r' (which is representing the recursive call |
| 74 | + 'mapFilter f xs'). If it is 'Just b', we put 'b' on the front of |
| 75 | + 'r' (compare the 'listIdentity' function above). -} |
| 76 | + |
| 77 | + |
| 78 | +{- 3. Above we saw that 'foldl' and 'foldr' in general give different |
| 79 | + answers. However, it is possible to define 'foldl' just by using |
| 80 | + 'foldr'. |
| 81 | +
|
| 82 | + First try to define a function that is the same as 'foldl', |
| 83 | + using 'foldr', 'reverse' and a '\' function: -} |
| 84 | + |
| 85 | +{- The key thing to notice is that the difference between 'foldl' and |
| 86 | + 'foldr' is that 'foldl' goes left-to-right and 'foldr' goes right |
| 87 | + to left. So it makes sense to reverse the input list. The function |
| 88 | + argument 'f' then takes its arguments in the wrong order, so we |
| 89 | + flip them using a little '\' function. -} |
| 90 | + |
| 91 | +foldlFromFoldrAndReverse :: (b -> a -> b) -> b -> [a] -> b |
| 92 | +foldlFromFoldrAndReverse f x xs = foldr (\a b -> f b a) x (reverse xs) |
| 93 | + |
| 94 | +{- We could have also used the 'flip' function from last week's |
| 95 | + questions, which is provided by the standard library: -} |
| 96 | + |
| 97 | +foldlFromFoldrAndReverse_v2 :: (b -> a -> b) -> b -> [a] -> b |
| 98 | +foldlFromFoldrAndReverse_v2 f x xs = foldr (flip f) x (reverse xs) |
| 99 | + |
| 100 | +{- Much harder: define 'foldl' just using 'foldr' and a '\' function: -} |
| 101 | + |
| 102 | +{- |
| 103 | +foldl :: (b -> a -> b) -> b -> [a] -> b |
| 104 | +foldl f a [] = a |
| 105 | +foldl f a (x:xs) = foldl f (f a x) xs |
| 106 | +-} |
| 107 | + |
| 108 | +-- This is quite a bit more complex than the other solution using |
| 109 | +-- 'reverse'. The key idea is to construct a "transformer" function |
| 110 | +-- with 'foldr' that acts like 'foldl' would. Try writing out some |
| 111 | +-- steps of this function with some example 'f's to see how it works. |
| 112 | + |
| 113 | +foldlFromFoldr :: (b -> a -> b) -> b -> [a] -> b |
| 114 | +foldlFromFoldr f a xs = foldr (\a g b -> g (f b a)) id xs a |
| 115 | + |
| 116 | +{- Remember from Week03 that 'id' is '\x -> x': the function that just |
| 117 | + returns its argument. -} |
| 118 | + |
| 119 | +{- Understanding 'foldlFromFoldr' may take a bit of work. The key point |
| 120 | + is that we use 'foldr' to build a /function/ from the input list |
| 121 | + 'xs' that will compute the left fold from any given initial value. |
| 122 | +
|
| 123 | + In more detail, the 'foldr' is used to build a function that takes |
| 124 | + an accumulator argument, similar to the 'fastReverse' function in |
| 125 | + Week01: |
| 126 | +
|
| 127 | + - The 'id' is the base case: it takes the accumulator and returns |
| 128 | + it (compare the first clause of 'foldl', which returns 'a'). |
| 129 | +
|
| 130 | + - The '\a g b -> g (f b a)' is the step case: |
| 131 | +
|
| 132 | + - 'a' is the value from the input list |
| 133 | + - 'g' is the result of processing the rest of the list, which a |
| 134 | + /function/ that is expecting an accumulator. |
| 135 | + - 'b' is the accumulator so far. |
| 136 | +
|
| 137 | + So this function combines the value 'a' and the accumulator 'b' |
| 138 | + using 'f', and passes that to 'g'. |
| 139 | +
|
| 140 | + So it is doing a 'fastReverse' and a 'foldr' at the same time (with |
| 141 | + the flipped arguments to 'f'), so can be seen as an optimised |
| 142 | + version of the first solution. |
| 143 | +
|
| 144 | + It may be helpful to understand the /types/ involved. We are |
| 145 | + writing a function with this type (the type of 'foldl'): |
| 146 | +
|
| 147 | + (b -> a -> b) -> b -> [a] -> b |
| 148 | +
|
| 149 | + and 'foldr' has this generic type: |
| 150 | +
|
| 151 | + (a -> b -> b) -> b -> [a] -> b |
| 152 | +
|
| 153 | + but we are *using* 'foldr' with this type: |
| 154 | +
|
| 155 | + foldr :: (a -> (b -> b) -> (b -> b)) -> -- 'step case' |
| 156 | + (b -> b) -> -- 'base case' |
| 157 | + [a] -> -- 'input list' |
| 158 | + b -> -- 'initial accumulator' |
| 159 | + b -- result |
| 160 | +
|
| 161 | + Note that the 'step case' takes a function and returns a function: |
| 162 | + we are building a /function/ by recursion. |
| 163 | +
|
| 164 | + Don't worry if you don't get this at the first few attempts. It |
| 165 | + takes some time to rewrite your mind to see functions as something |
| 166 | + that can be built incrementally by other functions! Looking at the |
| 167 | + types is usually a good way to not get lost. -} |
| 168 | + |
| 169 | + |
| 170 | + |
| 171 | +{- 4. The following is a datatype of Natural Numbers (whole numbers |
| 172 | + greater than or equal to zero), represented in unary. A natural |
| 173 | + number 'n' is represented as 'n' applications of 'Succ' to |
| 174 | + 'Zero'. So '2' is 'Succ (Succ Zero)'. Using the same recipe we |
| 175 | + used above for 'Tree's and 'Maybe's, work out the type and |
| 176 | + implementation of a 'fold' function for 'Nat's. -} |
| 177 | + |
| 178 | +data Nat |
| 179 | + = Zero -- a bit like [] |
| 180 | + | Succ Nat -- a bit like x : xs, but without the 'x' |
| 181 | + deriving Show |
| 182 | + |
| 183 | +foldNat :: (b -> b) -> b -> Nat -> b |
| 184 | +foldNat succ zero Zero = zero |
| 185 | +foldNat succ zero (Succ n) = succ (foldNat succ zero n) |
| 186 | + |
| 187 | +{- HINT: think about proofs by induction. A proof by induction has a |
| 188 | + base case and a step case. -} |
| 189 | + |
| 190 | +{- Here we have 'zero' for the base case, 'succ' for the step case. |
| 191 | +
|
| 192 | + As an example, we can define 'add' for 'Nat' in terms of 'foldNat', |
| 193 | + which has a similar structure to 'append' for lists: -} |
| 194 | + |
| 195 | +add :: Nat -> Nat -> Nat |
| 196 | +add x y = foldNat Succ y x |
| 197 | + |
| 198 | +{- 5. Write a list comprehension to generate all the cubes (x*x*x) of |
| 199 | + the numbers 1 to 10: -} |
| 200 | + |
| 201 | +cubes :: [Int] |
| 202 | +cubes = [ x*x*x | x <- [1..10] ] |
| 203 | + |
| 204 | + |
| 205 | +{- 6. The replicate function copies a single value a fixed number of |
| 206 | + times: |
| 207 | +
|
| 208 | + > replicate 5 'x' |
| 209 | + "xxxxx" |
| 210 | +
|
| 211 | + Write a version of replicate using a list comprehension: -} |
| 212 | + |
| 213 | +replicate' :: Int -> a -> [a] |
| 214 | +replicate' n a = [ a | _ <- [1..n]] |
| 215 | + |
| 216 | +{- 7. One-pass Average. |
| 217 | +
|
| 218 | + It is possible to use 'foldr' to |
| 219 | + implement many other interesting functions on lists. For example |
| 220 | + 'sum' and 'len': -} |
| 221 | + |
| 222 | +sumDoubles :: [Double] -> Double |
| 223 | +sumDoubles = foldr (\x sum -> x + sum) 0 |
| 224 | + |
| 225 | +lenList :: [a] -> Integer |
| 226 | +lenList = foldr (\_ l -> l + 1) 0 |
| 227 | + |
| 228 | +{- Putting these together, we can implement 'avg' to compute the average |
| 229 | + (mean) of a list of numbers: -} |
| 230 | + |
| 231 | +avg :: [Double] -> Double |
| 232 | +avg xs = sumDoubles xs / fromInteger (lenList xs) |
| 233 | + |
| 234 | +{- Neat as this function is, it is not as efficient as it could be. It |
| 235 | + traverses the input list twice: once to compute the sum, and then |
| 236 | + again to compute the length. It would be better if we had a single |
| 237 | + pass that computed the sum and length simultaneously and returned a |
| 238 | + pair. |
| 239 | +
|
| 240 | + Implement such a function, using foldr: -} |
| 241 | + |
| 242 | +sumAndLen :: [Double] -> (Double, Integer) |
| 243 | +sumAndLen = foldr (\x (sum, len) -> (x + sum, len + 1)) (0,0) |
| 244 | + |
| 245 | +-- NOTE: The solution combines the functions used in 'sumDoubles' and |
| 246 | +-- 'lenList' by making it take a pair '(sum,len)' as well as the list |
| 247 | +-- element 'x'. It then adds 'x' to the 'sum' part and '1' to the |
| 248 | +-- 'len' part. |
| 249 | + |
| 250 | +{- Once you have implemented your 'sumAndLen' function, this alternative |
| 251 | + average function will work: -} |
| 252 | + |
| 253 | +avg' :: [Double] -> Double |
| 254 | +avg' xs = total / fromInteger length |
| 255 | + where (total, length) = sumAndLen xs |
| 256 | + |
| 257 | +{- 8. mapTree from foldTree |
| 258 | +
|
| 259 | + Here is the 'Tree' datatype that is imported from the Week04 module: |
| 260 | +
|
| 261 | +data Tree a |
| 262 | + = Leaf |
| 263 | + | Node (Tree a) a (Tree a) |
| 264 | + deriving Show |
| 265 | +
|
| 266 | + As we saw in the lecture notes, it is possible to write a generic |
| 267 | + recursor pattern for trees, similar to 'foldr', copied here for reference: |
| 268 | +
|
| 269 | +foldTree :: b -> (b -> a -> b -> b) -> Tree a -> b |
| 270 | +foldTree l n Leaf = l |
| 271 | +foldTree l n (Node lt x rt) = n (foldTree l n lt) x (foldTree l n rt) |
| 272 | +
|
| 273 | + Your job is to implement 'mapTree' (from Week03) in terms of |
| 274 | + 'foldTree': -} |
| 275 | + |
| 276 | +mapTree :: (a -> b) -> Tree a -> Tree b |
| 277 | +mapTree f = foldTree Leaf -- Leaf case: 'Leaf's become 'Leaf's |
| 278 | + (\l x r -> Node l (f x) r) -- Node case: 'Node's become 'Node's, but with the data changed |
| 279 | + |
| 280 | +{- Here is the explicitly recursive version of 'mapTree', for |
| 281 | + reference: -} |
| 282 | + |
| 283 | +mapTree0 :: (a -> b) -> Tree a -> Tree b |
| 284 | +mapTree0 f Leaf = Leaf |
| 285 | +mapTree0 f (Node lt x rt) = Node (mapTree0 f lt) (f x) (mapTree0 f rt) |
| 286 | + |
| 287 | + |
| 288 | +{- 9. Finally, use 'foldTree' to flatten a tree to list in left-to-right |
| 289 | + order: -} |
| 290 | + |
| 291 | +flatten :: Tree a -> [a] |
| 292 | +flatten = foldTree [] -- Leaf case: has no elements, so is the empty list |
| 293 | + (\l x r -> l ++ [x] ++ r) -- Node case: append the left, middle, and right together |
0 commit comments