forked from pennstdropout/02-higherorder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSecretCode.hs
188 lines (143 loc) · 5.7 KB
/
SecretCode.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{-
---
fulltitle: "Optional exercise: SecretCode"
date: September 11, 2023
---
This module is for those who are completely new to functional programming.
It is an optional exercise, and will give you a chance to practice before
the first in-class exercise. If you have questions about this exercise,
please ask on the Ed platform or in office hours.
-}
module SecretCode where
{-
Remember that you can execute the definitions in this module by loading it
into ghci. In the terminal, you can use the command
stack ghci SecretCode.hs
to automatically start ghci and load the module.
OK, we're going to write a Haskell program to encode and decode text files
using a secret code.
We'll call it the Brown Fox code. Here's how it works:
- Replace each letter according to the following correspondence:
"abcdefghijklmnopqrstuvwxyz"
to "thequickbrownfxjmpsvlazydg"
But leave any non-letter characters alone.
- Then reverse the order of the lines in the file.
-}
import Data.Char
import Data.Maybe
import System.FilePath -- library for manipulating FilePaths.
import Test.HUnit
{-
First, we make a lookup list (aka association list) containing each pair of
corresponding letters:
-}
code :: [(Char, Char)]
code = zip ['a' .. 'z'] cypher ++ zip ['A' .. 'Z'] (map toUpper cypher)
where
cypher :: String
cypher = "thequickbrownfxjmpsvlazydg"
{-
Now, how can we use this lookup list?
Association lists seem like they are probably a pretty common idiom,
so let's check Hoogle to see if there is a function to look things up
in them automatically...
What type would such a function have? In our case, we want something
like:
[(Char,Char)] -> Char -> Char
That is, the function should take a list that maps `Char`s to `Char`s and a
specific `Char`, and it should return the corresponding `Char` from the list
(if it is present). The first hit on
[Hoogle](https://hoogle.haskell.org/?hoogle=%5B(Char%2CChar)%5D%20-%3E%20Char%20-%3E%20Char&scope=set%3Ahaskell-platform)
for this type is the standard library function `lookup`:
lookup :: Eq a => a -> [(a,b)] -> Maybe b
Ignoring the `Eq a` part for now (we'll talk about it next week), this
type makes a lot of sense. It's a bit more general than what we
searched for, allowing the function to be called with different types
for the "keys" and "values" of the association list to take on
different, and `lookup` also returns a `Maybe` type because the thing
we're looking up might not be in the list.
(Recall that a `Maybe a` is either `Just v` for some v of type `a`, or
Nothing.)
So, we can use `lookup` to encode a particular character. If we don't have a mapping for a
character in our code, (i.e. for punctuation) we should leave it alone.
-}
-- >>> encodeChar 'a'
-- 't'
-- >>> encodeChar '.'
-- '.
encodeChar :: Char -> Char
encodeChar c = undefined
testEncodeChar =
runTestTT $
TestList
[ encodeChar 'a' ~?= 't',
encodeChar '.' ~?= '.'
]
{-
We'll next need a way to encode a whole line of text. Of course, remembering
that `String`s are just lists of `Char`s, there is a perfect higher-order
function in `HigherOrder` that we can use:
-}
-- >>> encodeLine "abc defgh"
-- "the quick"
encodeLine :: String -> String
encodeLine = undefined
testEncodeLine = runTestTT $ TestList [encodeLine "abc defgh" ~?= "the quick"]
{-
Finally, we need a function to encode a whole file. Remember, we want to
reverse the order of the lines (so that the last line is first, and so on),
then swap the letters in each. The
[`reverse`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:reverse)
function in the standard library will come in handy.
However, we also need a way to break a file into
lines - we could do this manually by folding over the `String` and checking
for newlines, but this seems like a commonly used function, let's check
[Hoogle](https://hoogle.haskell.org/?hoogle=String%20-%3E%20%5BString%5D&scope=set%3Ahaskell-platform)
instead. Indeed, we find a function of type
String -> [String]
in the standard library to do just this. Furthermore, its
[counterpart](https://hoogle.haskell.org/?hoogle=String%20-%3E%20%5BString%5D&scope=set%3Ahaskell-platform)
of type
[String] -> String
So...
-}
encodeContent :: String -> String
encodeContent = undefined
testEncodeContent =
runTestTT $
encodeContent "abc\n defgh\n" ~?= " quick\nthe\n"
{-
Don't forget that the . operator is function composition. That is:
(f . g) x = f (g x)
See if you can simplify your definition of `encodeContent` using this
operator.
OK, now let's construct an IO action that actually reads in a file
from disk, encodes it, and writes it back out. We can look at the
Haskell [Prelude](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:readFile)
to find functions for reading and writing files.
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
Your function should read from the file 'f', but shouldn't overwrite the file
with the encoded version. In Haskell, `FilePath`s can be manipulated with
functions in the [System.FilePath
library](https://www.stackage.org/haddock/lts-18.10/filepath-1.4.2.1/System-FilePath-Posix.html).
For example, we can use the `replaceExtension` function to create a new file
name for output.
-}
encodeFile :: FilePath -> IO ()
encodeFile f =
if takeExtension f == "code"
then putStrLn "Cannot encode .code files"
else do
let outFilePath = replaceExtension f "code"
undefined
{-
Finally, let's put it all together into a "main" function that reads in
a file name from the standard input stream and swizzles it:
-}
main :: IO ()
main = do
putStrLn "What file shall I encode?"
fn <- getLine
encodeFile fn
putStrLn "All done!"