due at 23:59 on +80
Save all your functions into one file called a05.hs
and submit it to this dropbox.
mapFirstRest
Define the function mapFirstRest
, with this signature:
mapFirstRest :: (a -> b) -> (a -> b) -> [a] -> [b]
It works like map
, in that it applies a function to each element of a list. But unlike map
it takes two separate function parameters, and applies the first function to the first list element, and the second function to the rest of the list. Here are some examples:
ghci> mapFirstRest (+5) (*5) [7..10]
[12,40,45,50]
ghci> mapFirstRest (+5) (*5) [8,8,8]
[13,40,40]
ghci> mapFirstRest length (const 0) ["Alice", "Bob", "Carol"]
[5,0,0]
ghci> mapFirstRest (+5) id [9,9,9]
[14,9,9]
ghci> mapFirstRest id (+5) [9,9,9]
[9,14,14]
In the above, we’re using id
which is the identity function. It just returns its argument exactly as given:
ghci> id 9
9
ghci> id "Hello"
"Hello"
capitalize
Create a function capitalize
that will convert the first letter of its string argument to upper-case.
capitalize :: String -> String
To do that, you should import Data.Char
which contains a function toUpper
that works on a single character:
ghci> import Data.Char
ghci> toUpper 'h'
'H'
ghci> toUpper 'G'
'G'
ghci> toUpper '%'
'%'
Use mapFirstRest
to apply toUpper
appropriately. Here are some sample inputs and outputs:
ghci> capitalize "hello"
"Hello"
ghci> capitalize "nifty"
"Nifty"
ghci> capitalize "NICE"
"NICE"
ghci> capitalize "#wow"
"#wow"
maybeCapitalize
, titleCase
Title case is the name for the capitalization rules typically used in titles and headlines, where every word is capitalized except articles (the
, a
), conjunctions (and
, but
, for
), and prepositions (in
, of
). But the first word of the sentence is capitalized even if it is one of these words.
Define these in your solution:
exemptions :: [String]
maybeCapitalize :: String -> String
titleCase :: String -> String
The exemptions
should be a list of strings representing words that are exempt from capitalization in titles:
ghci> "the" `elem` exemptions
True
ghci> "dog" `elem` exemptions
False
The maybeCapitalize
is a function that capitalizes only if the word is not exempt. If it is exempt, then it just returns the word unchanged. In your definition, you should use both exemptions
and capitalize
.
ghci> maybeCapitalize "the"
"the"
ghci> maybeCapitalize "dog"
"Dog"
Finally, titleCase
is a function that takes a complete phrase, splits it into words, and applies capitalization appropriately. Your implementation should use capitalize
, maybeCapitalize
, mapFirstRest
, words
, unwords
, and function composition.
The built-in functions words
and unwords
are great for disassembling a phrase into a list of words, and then putting it back together again:
ghci> words "the quick brown fox"
["the","quick","brown","fox"]
ghci> unwords ["jumps", "over", "the", "lazy", "dog."]
"jumps over the lazy dog."
Here are some examples for titleCase
:
ghci> titleCase "the quick brown fox jumps over the lazy dog"
"The Quick Brown Fox Jumps Over the Lazy Dog"
ghci> titleCase "the hound of the baskervilles"
"The Hound of the Baskervilles"
ghci> titleCase "harry potter and the chamber of secrets"
"Harry Potter and the Chamber of Secrets"
Using the definition of Tree
from the notes on 5 October, define traversals that concatenate together all the values in the specified order. Your functions will require that the type of values stored in the tree forms a monoid.
preorder :: Monoid a => Tree a -> a
inorder :: Monoid a => Tree a -> a
postorder :: Monoid a => Tree a -> a
At each node:
Here are traversals on the tree defined sample1
in the notes (and a reminder of what that tree contains).
ghci> printTree sample1
- "A"
|- "K"
| |- "M"
| | |- *
| | |- "Q"
| |- "P"
|- *
ghci> preorder sample1
"AKMQP"
ghci> inorder sample1
"MQKPA"
ghci> postorder sample1
"QMPKA"
Using the BoundedStack
data type from the Assignment 4 solutions, define it as an instance of Monoid
. Unlike the tree, it should not require that the element type is also a Monoid
. Here is the first line of the instance definition:
instance Monoid (BoundedStack a) where
and then you add the definitions of mempty
and mappend
.
To append two stacks, you should combine together their capacities and place all the elements in the left stack before all the elements in the right stack. Here are some examples:
ghci> Just s1 = push 7 (new 3)
ghci> Just s2 = (push 8 >=> push 9) (new 2)
ghci> s1 <> s2
BoundedStack {capacity = 2, elements = [7,9,8]}
ghci> s2 <> mempty
BoundedStack {capacity = 0, elements = [9,8]}
ghci> mempty <> s1
BoundedStack {capacity = 2, elements = [7]}
Remember that <>
is an operator shorthand for mappend
.
import Data.Monoid
import Data.Char
import Control.Monad.State
main = do
flip execStateT (0,0) $ do
-- mapFirstRest
verify "1.01" [12,40,45,50] $ mapFirstRest (+5) (*5) [7..10]
verify "1.02" [13,40,40] $ mapFirstRest (+5) (*5) [8,8,8]
verify "1.03" [5,0,0] $ mapFirstRest length (const 0)
["Alice", "Bob", "Carol"]
verify "1.04" [14,9,9] $ mapFirstRest (+5) id [9,9,9]
verify "1.05" [9,14,14] $ mapFirstRest id (+5) [9,9,9]
-- capitalize
verify "2.01" "Hello" $ capitalize "hello"
verify "2.02" "Nifty" $ capitalize "nifty"
verify "2.03" "NICE" $ capitalize "NICE"
verify "2.04" "#wow" $ capitalize "#wow"
-- maybeCapitalize
assert "3.01" $ "the" `elem` exemptions
assert "3.02" $ not $ "dog" `elem` exemptions
verify "3.03" "the" $ maybeCapitalize "the"
verify "3.04" "Dog" $ maybeCapitalize "dog"
verify "3.05" "The Quick Brown Fox Jumps Over the Lazy Dog"
$ titleCase "the quick brown fox jumps over the lazy dog"
verify "3.06" "The Hound of the Baskervilles"
$ titleCase "the hound of the baskervilles"
verify "3.07" "Harry Potter and the Chamber of Secrets"
$ titleCase "harry potter and the chamber of secrets"
-- tree traversal
verify "4.01" "AKMQP" $ preorder sample1
verify "4.02" "MQKPA" $ inorder sample1
verify "4.03" "QMPKA" $ postorder sample1
verify "4.04" "BSCDFR" $ preorder sample2
verify "4.05" "SDCFBR" $ inorder sample2
verify "4.06" "DFCSRB" $ postorder sample2
-- stack monoid
let Just s1 = push 7 (new 3)
let Just s2 = (push 8 >=> push 9) (new 2)
verify "5.01" (BoundedStack {capacity = 2, elements = [7,9,8]})
$ s1 <> s2
verify "5.02" s2 $ s2 <> mempty
verify "5.03" s1 $ mempty <> s1
where
say = liftIO . putStrLn
correct (k, n) = (k+1, n+1)
incorrect (k, n) = (k, n+1)
assert s = verify s True
verify :: (Show a, Eq a) => String -> a -> a -> StateT (Int,Int) IO ()
verify = verify' (==)
verifyF = verify' (\x y -> abs(x-y) < 0.00001)
verify' :: (Show a) => (a -> a -> Bool) -> String -> a -> a ->
StateT (Int,Int) IO ()
verify' eq tag expected actual
| eq expected actual = do
modify correct
say $ " OK " ++ tag
| otherwise = do
modify incorrect
say $ "ERR " ++ tag ++ ": expected " ++ show expected
++ " got " ++ show actual
-- End of test driver