Commit 269ac744 authored by Sandra Dylus's avatar Sandra Dylus
Browse files

Add solutions to remaining exercises

parent 81224b67
> {-# LANGUAGE GADTSyntax #-}
>
> module Functional.Exercise.Exercise3 where
>
> import Functional.Lecture.Recursion
> import qualified Functional.Exercise.Exercise2 as Ex
1) Given the following function definitions, give an equivalent definition using lambda functions.
> func1 :: Bool -> Integer -> Integer -> Integer
> func1 b n m = if not b then n else m
>
> func2 :: Integer -> Integer
> func2 n = n + n
>
> func3 :: Integer -> Integer -> Integer
> func3 n m = n + m * m
> func1Lambda :: Bool -> Integer -> Integer -> Integer
> func1Lambda = \ b n m -> if not b then n else m
>
> func2Lambda :: Integer -> Integer
> func2Lambda = \ n -> n + n
>
> func3Lambda :: Integer -> Integer -> Integer
> func3Lambda = \ n m -> n + m * m
For example, for the function
> func :: Integer -> Bool -> Integer
> func n b = if b then n else 42
an equivalent definition using a lambda function looks as follows.
> funcLambda :: Integer -> Bool -> Integer
> funcLambda = \n b -> if b then n else 42
2) Given the `IntList` type from the lecture notes about "Recursion".
data IntList where
Nil :: IntList
Cons :: Integer -> IntList -> IntList
Define the following higher-order functions `mapList` and `filterList` that follow the same abstraction pattern as the functions we defined for `CoordMap` in the lecture.
> mapList :: (Integer -> Integer) -> IntList -> IntList
> mapList f Nil = Nil
> mapList f (Cons i list) = Cons (f i) (mapList f list)
>
> filterList :: (Integer -> Bool) -> IntList -> IntList
> filterList p Nil = Nil
> filterList p (Cons i list) = if p i then Cons i (filterList p list) else filterList p list
3) Using these definitions of `mapList` and `filterList`, define the following functions that increment all `Integer` values of a given `IntList` and keep the `Integer`-values that are greater than 5, respectively.
> incList :: IntList -> IntList
> incList iList = mapList func iList
> where func x = x + 1
> greater5List :: IntList -> IntList
> greater5List iList = filterList pred iList
> where pred x = x > 5
The functions should behave as follows.
> incList (Cons 1 (Cons 2 (Cons 3 Nil)))
Cons 2 (Cons 3 (Cons 4 Nil))
> greater5List (Cons 2 (Cons 12 (Cons 5 Nil))
Cons 12 Nil
In a second step use lambda functions instead of a local function definition.
> incList' :: IntList -> IntList
> incList' iList = mapList (\x -> x + 1) iList
> greater5List' :: IntList -> IntList
> greater5List' iList = filterList (\x -> x > 5) iList
4) Given the `Field` data type from the last exercises.
data Token where
Blank :: Token
Block :: Token
data Row where
EmptyR :: Row
ARow :: Token -> Row -> Row
data Field where
EmptyF :: Field
AField :: Row -> Field -> Field
We implemented functions `replaceTokenInRow` and `replaceTokenInField`. Reimplement these functions using the higher-order helper functions `mapRow` and `mapField`. In order to avoid name clashes we imported the corresponding module qualified by using `Ex`.
> mapRow :: (Ex.Token -> Ex.Token) -> Ex.Row -> Ex.Row
> mapRow f Ex.EmptyR = Ex.EmptyR
> mapRow f (Ex.ARow t row) = Ex.ARow (f t) (mapRow f row)
>
> mapField :: (Ex.Row -> Ex.Row) -> Ex.Field -> Ex.Field
> mapField f Ex.EmptyF = Ex.EmptyF
> mapField f (Ex.AField row field) = Ex.AField (f row) (mapField f field)
>
> replaceTokenInRow :: Ex.Token -> Ex.Row -> Ex.Row
> replaceTokenInRow tok row = mapRow (\_ -> tok) row
>
> replaceTokenInField :: Ex.Token -> Ex.Field -> Ex.Field
> replaceTokenInField tok field = mapField (\row -> replaceTokenInRow tok row) field
<<<------------- Exercises corresponding to lecture on 16/12/19 ------------->>>
5) Now that we know about polymorphic structures, let us redefine the `Field` type as follows.
> -- type Field token = [[token]]
> type Field token = [] ([] token)
Here, instead of reusing the `Token`s defined in the last exercise, we use a type parameter in order to use the type synonym `Field` with abritrary values.
Reimplement the function `replaceTokenInField` once more using the predefined higher-order function `map` that works an arbitrary lists; note that for this concrete example, we reuse `Ex.Token`.
> replaceToken :: Ex.Token -> Field Ex.Token -> Field Ex.Token
> replaceToken tok field = map (\row -> map (\t -> tok) row) field
6) We also redefined `CoordMap` using polymorphic lists and pairs.
> -- type Coord = (Int,Int)
> type Coord = (,) Int Int
> -- type Coords = [Coord]
> type Coords = [] Coord
Based on this type synonym we define a type to represent `Coordinate`s as keys and arbitrary tokens as values in order to describe the positions of such tokens. A list of such pairs describes a figure.
> -- type Figure token = [(Coord, token)]
> type Figure token = [] ((,) Coord token)
Here, we again use a type parameter `token` to use `Figure` with an abitrary type.
Using this type, we can visualise, for example, the following figure.
> exFigure1 :: Figure Ex.Token
> exFigure1 = [ ((2,1), Ex.Block), ((2,5), Ex.Block), ((3,2), Ex.Block), ((3,4), Ex.Block)
> , ((4,3), Ex.Block), ((5,2), Ex.Block), ((6,1), Ex.Block) ]
5 | #
4 | #
3 | #
2 | # #
1 | # #
_ _ _ _ _ _
1 2 3 4 5 6
Define a function `lookupCoord` that checks if a given figure contains the specific coordinate: if yes the function should yield the associated token and `Nothing` otherwise.
> lookupCoord :: Coord -> Figure token -> Maybe token
> lookupCoord coord ((figCoord,tok) : figs) = if coord == figCoord then Just tok else lookupCoord coord figs
7) Define a function `blocks` that yields a figure with only `Ex.Block`-values at the coordinates given as first argument.
> blocks :: Coords -> Figure Ex.Token
> blocks coords = map (\coord -> (coord,Ex.Block)) coords
Using this function we can redefined `exFigure1` as follows.
> exFigure1' = blocks [(2,1), (2,5), (3,2), (3,4), (4,3), (5,2), (6,1)]
> {-# LANGUAGE GADTSyntax #-}
>
> module Functional.Exercise.Exercise4 where
1) Given the following definitions and the predefined function `take`, `map` and `zip`.
> loop :: a
> loop = loop
>
> nats :: [] Int
> nats = nats' 0
> where nats' n = n : nats' (n+1)
Which of the following expressions terminate?
a) take 10 nats
b) map (\x -> x + 1) nats
c) zip loop [1,2,3,4,5]
d) take 13 (map (\x -> x + 1) nats)
e) zip [True, False, True, True] nats
a) terminates
b) does not terminate
c) does not terminate
d) terminates
d) terminates
2) We define a polymorphic data type for binary trees with (polymorphic) elements in their leaves as follows.
> data Tree a where
> Leaf :: a -> Tree a
> Node :: Tree a -> Tree a -> Tree a
That is, we can represent a tree with one value using `Leaf` and a tree with two subtrees using `Node`.
We can define a mapping and a folding function for `Tree a` with the following types.
> mapTree :: (a -> b) -> Tree a -> Tree b
> mapTree f (Leaf val) = Leaf (f val)
> mapTree f (Node t1 t2) = Node (mapTree f t1) (mapTree f t2)
>
> foldTree :: (a -> b) -> (b -> b -> b) -> Tree a -> b
> foldTree leafF nodeF (Leaf val) = leafF val
> foldTree leafF nodeF (Node t1 t2) = nodeF (foldTree leafF nodeF t1) (foldTree leafF nodeF t2)
Observe that these definitions following the same scheme as for lists: a mapping function applies its functional argumt to each element of the structure (in case of trees, the elements occur in the `Leaf`-constructor); a folding function describes how to transform a structure to an arbitrary type by specifying the function to replace the constructors with.
Now define the following functions using `mapTree` and `foldTree`, respectively.
> negateTree :: Tree Bool -> Tree Bool
> negateTree t = mapTree elemF t
> where
> elemF b = not b
>
> sumTree :: Tree Int -> Int
> sumTree t = foldTree leafF nodeF t
> where
> leafF i = i
> nodeF sum1 sum2 = sum1 + sum2
>
> values :: Tree Int -> [Int]
> values t = foldTree leafF nodeF t
> where
> leafF i = [i]
> nodeF list1 list2 = list1 ++ list2
3) The `foldList` function we defined in the lecture is predefined as `foldr :: (a -> b -> b) -> b -> [a] -> b` in Haskell. Reimplement the function `countElem` given in the lecture using `foldr`.
> countElem :: [] Int -> Int -> Int
> countElem [] elemToFind = 0
> countElem (val:list) elemToFind = if val == elemToFind
> then 1 + countElem list elemToFind
> else countElem list elemToFind
> countElem :: [] Int -> Int -> Int
> countElem list elemToFind = foldr consF emptyF list
> where
> consF i count = if i == elemToFind then count + 1 else count
> emptyF = 0
4) Given the following data type to represent arithmetic expression consisting of numeric values, an addition or multiplication.
> data Expr where
> Num :: Int -> Expr
> Add :: Expr -> Expr -> Expr
> Mult :: Expr -> Expr -> Expr
Give three exemplary values for arithmetic expressions that represent the expression given in the comment above.
> -- 1 + (2 * 3)
> expr1 :: Expr
> expr1 = Add (Num 1) (Mult (Num 2) (Num 3))
>
> -- (1 + 2) * 3
> expr2 :: Expr
> expr2 = Mult (Add (Num 1) (Num 2)) (Num 3)
>
> -- (1 + 2) * (3 + 4)
> expr3 :: Expr
> expr3 = Mult (Add (Num 1) (Num 2)) (Add (Num 3) (Num 4))
Define a function `value` that interpretes the `Expr`-data type by replacing the constructors `Add` and `Mult` with the concrete addition and multiplication operator.
> value :: Expr -> Int
> value (Num i) = i
> value (Add e1 e2) = value e1 + value e2
> value (Mult e1 e2) = value e1 * value e2
For example, the function should yield the following result for the above expressions.
$> value expr1
7
$> value expr2
9
$> value expr3
21
Complete the signature for the mapping and folding function corresponding to the given type and implement these functions. The mapping function enables us to apply a function on the `Int` occurs in the `Num`-constructor. The folding function is a higher-order function that enables us to exchange the constructors of a given `Expr`-values with functions in order to compute a new value.
> mapExpr :: (Int -> Int) -> Expr -> Expr
> mapExpr f (Num i) = Num (f i)
> mapExpr f (Add e1 e2) = Add (mapExpr f e1) (mapExpr f e2)
>
> foldExpr :: (Int -> b) -> (b -> b -> b) -> (b -> b -> b) -> Expr -> b
> foldExpr fNum fAdd fMult (Num i) = fNum i
> foldExpr fNum fAdd fMult (Add e1 e2) =
> fAdd (foldExpr fNum fAdd fMult e1) (foldExpr fNum fAdd fMult e2)
> foldExpr fNum fAdd fMult (Mult e1 e2) =
> fMult (foldExpr fNum fAdd fMult e1) (foldExpr fNum fAdd fMult e2)
Now define `value` by means of `foldExpr`.
> valueFold :: Expr -> Int
> valueFold expr = foldExpr id (+) (*) expr
5) Define a function `repeatValue` that computes an infinite list containing the value given as argument.
> repeatValue :: a -> [] a
> repeatValue x = x : repeatValue x
Define a function `replicateValue` that takes an `n :: Int` as first argument and yields a list of length `n` containing the value given as second argument. Use `repeatValue` and `take` to define the function.
> replicateValue :: Int -> a -> [] a
> replicateValue 0 x = []
> replicateValue n x = x : replicateValue (n-1) x
> {-# LANGUAGE GADTSyntax #-}
>
> module Functional.Exercise.Exercise5 where
>
> import Functional.Lecture.InputOutput (getNumber)
> import Data.Char (ord, chr)
1. In Haskell `String`s are list of `Char`s. The following definitions should transform the given string "HelloWorld" such that it yields the string specified by the comment above. You are only allow to use generic list functions like `map`, `foldr`, `filter`, but no manual pattern matching, for the implementation.
> -- str1 "HelloWorld" = "HelloWorld!"
> str1 :: String -> String
> str1 str = foldr (:) "!" str
>
> -- str2 "HelloWorld" = "IfmmpXpsme"
> str2 :: String -> String
> str2 str = map (\ch -> chr (ord ch + 1)) str
>
> -- str3 "HelloWorld" = "HllWrld"
> str3 :: String -> String
> str3 str = filter (\ch -> not (vowel ch)) str
> where
> vowel c = elem c "aeiou"
2. Implement a function `getInBounds :: Int -> Int -> IO Int` that reads a number from the user. If the value is not within the given bounds, the user needs to try again to type in a number that meets the bounds. The implementation should additionally behave as illustrated below.
$> getInBounds 12 16
Please type in a number between 12 and 16.
131
The number does not meet the given bounds, please try again.
13s
This input is not a number, please try again.
13
Hint: You should reuse `getNumber` and `inBounds` that we defined in previous lectures.
> getInBounds :: Int -> Int -> IO Int
> getInBounds min max = putStrLn ("Please type in a number between " ++ show min ++ " and " ++ show max ++ ".") >>
> getNumber >>= (\n ->
> if inBounds n (max, min)
> then pure n
> else putStrLn "The number does not meet the given bounds, please try again" >> getInBounds min max)
>
> inBounds :: Int -> (Int,Int) -> Bool
> inBounds x (xMax, xMin) = (x <= xMax) && (x >= xMin)
3. Implement a more general function `geStringWithCondition :: (String -> Bool) IO String` that reads a string from the user and only yield this value if the string obeys to the corresponding prediate. If the predicate does not hold, the user should be informed to try it again.
> getStringWithCondition :: (String -> Bool) -> IO String
> getStringWithCondition pStr = getLine >>= \str ->
> if pStr str
> then pure str
> else putStrLn "Try again." >> getStringWithCondition pStr
4. Implement an interactive number guessing game. The game `guessNumber` expects the "secret number" as argument and gives feedback for each guess and the game is completed, if the guessed number matches the secret. Otherwise the user gets feedback if the guess was "too small" or "too large".
Hint: Reuse the function `getNumber` from the lecture.
> guessNumber :: Int -> IO ()
> guessNumber secretNumber =
> putStrLn "Let's try to guess the number!" >>
> gameLoop
> where
> gameLoop = getNumber >>= (\userGuess ->
> if userGuess == secretNumber
> then putStrLn "Congrats, you guessed the number!"
> else if userGuess < secretNumber
> then putStrLn "Your guess is too small. Try again." >> gameLoop
> else putStrLn "Your guess is too large. Try again." >> gameLoop)
A round might look like this! Note, that this is a two-player game: the user starting the game shouldn't be the one guessing the numbers ; )
$> guessNumber 421
Let's try to guess the number!
12
Your guess is too small. Try again.
100
Your guess is too small. Try again.
200
Your guess is too small. Try again.
300
Your guess is too small. Try again.
400
Your guess is too small. Try again.
500
Your guess is too large. Try again.
450
Your guess is too large. Try again.
425
Your guess is too large. Try again.
413
Your guess is too small. Try again.
420
Your guess is too small. Try again.
422
Your guess is too large. Try again.
421
Congrats, you guessed the number!
5. Give the types for the following expressions.
a) `map ((+) 1)`
b) `[(+) 1, (*) 2, div 3]`
c) `foldr (+)`
d) `filter ((>) 4)`
e) `map (*)`
a) [] Int -> [] Int
b) [Int -> Int]
c) Int -> [] Int -> Int
d) [] Int -> [] Int
e) [] Int -> [] (Int -> Int)
6. Which of these type signatures are valid for the function `map`.
a) `map :: (Int -> Bool) -> [] Int -> [] Bool`
b) `map :: (Int -> Bool) -> [] Bool -> [] Int`
c) `map :: (Bool -> String) -> ([] Bool -> [] String)`
d) `map :: Bool -> (Bool -> [] Bool -> [] Bool)`
e) `map :: String -> (Bool -> [] String) -> [] Bool`
a) valid
b) not valid
c) valid
d) not valid
e) not valid
7. We want to represent a set of `Int` values as function `Int -> Bool`, that is, the resulting `Bool` indicates if the argument passed is part of the set (for `True`) or not (for `False`).
> type Set = Int -> Bool
We can, for example, define the empty set as follows.
> empty :: Set
> empty = \_ -> False
Since our representation of `Set` is a function, the empty set is the function that yields `False` for every argument: because no value is part of the empty set! That is, the representation of our function is based on the "lookup"-function (we used the first idea in the lecture (see `Misc.hs` for reference)).
The idea becomes more clear when we "inline" the type synonym `Set` (here we use an additional comment) and rename the `set`-variable to `isInSet` to indicate that this argument is a function that yields a boolean value (i.e., a predicate).
> isElem :: Int -> Set -> Bool
> -- isElem val set = set val
> -- isElem :: Int -> (Int -> Bool) -> Bool
> isElem val isInSet = isInSet val
> -- Yields `True` if the given `Int`-value is part of the `Set`, `False` otherwise.
> isElem :: Int -> Set -> Bool
> isElem val isInSet = isInSet val
Based on this representation, define the following functions.
> -- Inserts the first argument to the `Set`.
> insert :: Int -> Set -> Set
> insert insertVal isInSet = \lookupVal -> lookupVal == insertVal || isInSet lookupVal
>
> -- The new set should yield `True` if a value is in the first set or if it is part of the second set.
> union :: Set -> Set -> Set
> union isInSet1 isInSet2 = \lookupVal -> isInSet1 lookupVal || isInSet2 lookupVal
>
> -- The new set should yield `True` if a value is in the first set and of the second set.
> intersection :: Set -> Set -> Set
> intersection isInSet1 isInSet2 = \lookupVal -> isInSet1 lookupVal && isInSet2 lookupVal
For testing purposes, you want the following properties to hold.
$> isElem 5 empty
False
$> isElem 12435 empty
False
$> isElem 5 (insert 5 empty)
True
$> isElem 5 (union (insert 4 empty) (insert 3 empty))
False
$> isElem 4 (union (insert 4 empty) (insert 3 empty))
True
$> isElem 3 (union (insert 4 empty) (insert 3 empty))
True
$> isElem 4 (intersection (insert 4 empty) (insert 3 empty))
False
$> isElem 4 (intersection (insert 4 empty) (insert 4 empty))
You can also use the following function `fromList` to convert a list into a `Set` representation in order to test your implementation with "larger" sets. Note, that you need to implement `insert` first ; )
> toList :: [] Int -> Set
> toList list = foldr insert empty list
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment