Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
|
pp:other-exercises [2021/03/13 14:39] pdmatei created |
pp:other-exercises [2021/03/21 08:14] (current) pdmatei |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ===== Matrices ====== | ||
| + | |||
| + | ==== Haskell implementation of foldl ==== | ||
| + | |||
| + | In Haskell, ''foldl'' is implemented as follows: | ||
| + | <code> | ||
| + | foldl op acc [] = acc | ||
| + | foldl op acc (h:t) = foldl op (op acc h) t | ||
| + | </code> | ||
| + | |||
| + | Note the call of the ''op'' function. | ||
| + | |||
| + | ==== An exercise in modular programming with higher-order functions ==== | ||
| + | |||
| + | Let us consider the task of matrix multiplication in Haskell. Example: | ||
| + | <code> | ||
| + | 1 2 3 1 0 0 1+3 2 2+3 4 2 5 | ||
| + | 4 5 6 x 0 1 1 = 4+6 5 5+6 = 10 5 11 | ||
| + | 7 8 9 1 0 1 7+9 8 8+9 16 8 17 | ||
| + | </code> | ||
| + | |||
| + | |||
| + | === Matrix representation in Haskell === | ||
| + | |||
| + | The basic representation building-block in Haskell is the list. We can represent matrices in Haskell as a list where each element is a row (hence a list of elements). Example: | ||
| + | |||
| + | <code haskell> | ||
| + | m = [[1,2,3],[4,5,6],[7,8,9]] | ||
| + | </code> | ||
| + | |||
| + | A good warmup exercise is to write a nice display function for matrices. We transform each element of a line into a string: | ||
| + | |||
| + | <code haskell> | ||
| + | displayline l = map (\e->(show e)++" ") l | ||
| + | </code> | ||
| + | |||
| + | The code can be improved for legibility: | ||
| + | <code haskell> | ||
| + | displayline = map ((++" ").show) | ||
| + | </code> | ||
| + | |||
| + | |||
| + | Next, we fold the list into a string with a newline character: | ||
| + | |||
| + | <code haskell> | ||
| + | displayline l = foldr (++) "\n" (map ((++" ").show) l) | ||
| + | </code> | ||
| + | |||
| + | and simplify again, by expressing the pipeline function calls as functional composition: | ||
| + | <code haskell> | ||
| + | displayline :: Show a => [a] -> [Char] | ||
| + | displayline = (foldr (++) "\n") . (map ( (++ " ") . show ) ) | ||
| + | </code> | ||
| + | |||
| + | Note that we need to explicitly state the type of display. Sometimes, in Haskell, an explicit type declaration is required. For now, we omit details. | ||
| + | |||
| + | Next, we apply the above process on all matrix lines: | ||
| + | |||
| + | <code haskell> | ||
| + | display :: Show a => [[a]] -> [[Char]] | ||
| + | display = | ||
| + | let bind = foldr (++) "\n" | ||
| + | in bind.(map (bind . (map ((++" ") . show ) ) ) ) | ||
| + | </code> | ||
| + | |||
| + | Notice that we have separated the binding process, because we reuse it. | ||
| + | |||
| + | Finally, we make all matrices displayable: | ||
| + | <code haskell> | ||
| + | instance (Show a) => Show [[a]] where | ||
| + | show = | ||
| + | let bind = foldr (++) "\n" | ||
| + | in bind.(map (bind.(map ((++" ").show ) ) ) ) | ||
| + | </code> | ||
| + | More details about this implementation (e.g. instances, classes) will be given in future lectures. | ||
| + | | ||
| + | === Matrix multiplication === | ||
| + | |||
| + | == Step 1: Transposition == | ||
| + | |||
| + | Matrix multiplication operates on the **lines** of the first matrix and **columns** of the second. We transpose the second matrix, so that we now operate on lines on both matrices. The following code extracts **the first line** from a matrix ''m'': | ||
| + | <code haskell> | ||
| + | map head m | ||
| + | </code> | ||
| + | A matrix ''m'' **without** its first column is: | ||
| + | <code haskell> | ||
| + | map tail m | ||
| + | </code> | ||
| + | Finally transposition is given by: | ||
| + | <code haskell> | ||
| + | transpose ([]:_) = [] | ||
| + | transpose m = (map head m) : transpose (map tail m) | ||
| + | </code> | ||
| + | Notice that the //basis case// corresponds to a **list containing empty lists**. | ||
| + | |||
| + | == Step 2: Computing multiplication == | ||
| + | |||
| + | To compute the ''i,j''th **element** of the multiplication matrix, we need to multiply per element the ''i''th line by the ''j''th column: | ||
| + | <code haskell> | ||
| + | zipWith (*) li cj | ||
| + | </code> | ||
| + | and then add-up the values: | ||
| + | <code haskell> | ||
| + | foldr (+) 0 (zipWith (*) li cj) | ||
| + | </code> | ||
| + | |||
| + | To obtain the ''i''th **line** of the multiplication matrix, we need to repeat the above process **for each column of the second matrix**, in other words, for each line of its transposition: | ||
| + | |||
| + | <code haskell> | ||
| + | map (\col -> foldr (+) 0 (zipWith (*) li col) ) (transpose m2) | ||
| + | </code> | ||
| + | |||
| + | Finally, to obtain the multiplication matrix, we need to compute all its lines, hence: | ||
| + | <code haskell> | ||
| + | mult m1 m2 = | ||
| + | map (\line -> map (\col -> foldr (+) 0 (zipWith (*) line col) ) (transpose m2) ) m1 | ||
| + | </code> | ||
| + | |||
| + | === Matrices as images === | ||
| + | |||
| + | A matrix can be used to represent a **rasterized image** (a collection of pixels). In this example, we consider that pixels can have values: ' ' (white), '.' (grey) and '*' (black). | ||
| + | |||
| + | Higher-order functions can be naturally used to represent image-transformations, for instance, flipping: | ||
| + | |||
| + | <code haskell> | ||
| + | flipH = map reverse | ||
| + | flipV = reverse | ||
| + | </code> | ||
| + | |||
| + | Rotations: | ||
| + | <code haskell> | ||
| + | rotate90left = flipV.transpose | ||
| + | rotate90right = flipH.transpose | ||
| + | </code> | ||
| + | |||
| + | The //negative// of an image: | ||
| + | <code haskell> | ||
| + | invert = map (map (\x->if x=='*' then ' ' else '*') ) | ||
| + | </code> | ||
| + | |||
| + | Scaling an image horizontally: | ||
| + | <code haskell> | ||
| + | scalex = foldr (\h t->h:h:t) [] | ||
| + | </code> | ||
| + | |||
| + | Scaling vertically: | ||
| + | <code haskell> | ||
| + | scaley = map scalex | ||
| + | </code> | ||
| + | |||
| + | Balanced scale: | ||
| + | <code haskell> | ||
| + | scale = scalex . scaley | ||
| + | </code> | ||
| + | |||
| + | Or just a random sequence of operations: | ||
| + | <code haskell> | ||
| + | rand = foldr (.) id [rotate90left, invert, scale] | ||
| + | </code> | ||
| + | |||
| ===== Function composition and function application ===== | ===== Function composition and function application ===== | ||
| Line 30: | Line 190: | ||
| <code haskell> | <code haskell> | ||
| f l = | f l = | ||
| - | </code> | ||
| - | |||
| - | 31. Write a function which takes a list of pairs - student-name and grade, removes those with grades less than 5, splits the name in substrings, and adds one bonus point to people with three names. Example: | ||
| - | <code haskell> | ||
| - | f [("Dan Matei Popovici",9),("Mihai",4),("Andrei Alex",6)] = | ||
| - | [(["Dan", "Matei", "Popovici"],10),(["Andrei,Alex"],6)] | ||
| - | </code> | ||
| - | |||
| - | 32. Write the signature and implement the following function: | ||
| - | <code haskell> | ||
| - | f ["Matei", "Mihai"] ["Popovici","Dumitru"] = [("Matei","Popovici"), ("Mihai","Dumitru")] | ||
| - | </code> | ||
| - | |||
| - | 33. Implement the following function: | ||
| - | <code haskell> | ||
| - | f ["Matei", "Mihai"] ["Popovici","Dumitru"] = ["MPopovici", "MDumitru"] | ||
| </code> | </code> | ||