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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
module Hare where
import Data.Traversable
import Data.Word
import Data.List
import Data.Maybe
import Control.Monad.State
import Test.QuickCheck
import GHC.Enum
import RoverInterface
import RoverModel
-- PART 1: FINDING WAYPOINTS
data Path wp
= From wp
| GoTo (Path wp) wp
deriving (Eq)
instance Show wp => Show (Path wp) where
show (From x) = "From " ++ show x
show (GoTo xs x) = show xs ++ " >:> " ++ show x
-- Problem 1. Define a function `wf` that returns `True`
-- precisely if a given `Path` is well-formed according
-- to the rules set out in the specification.
-- turn recrsive path into more managable list of waypoints
recurse_waypoints :: (Wp wp) => Path wp -> [wp] -> [wp]
recurse_waypoints (From wp) paths = paths ++ [wp]
recurse_waypoints (GoTo p wp) paths = paths ++ [wp] ++ (recurse_waypoints p paths)
path_to_waypoints :: (Wp wp) => Path wp -> [wp]
path_to_waypoints p = recurse_waypoints p []
get_waypoint :: (Wp wp) => Path wp -> wp
get_waypoint (From wp) = wp
get_waypoint (GoTo p wp) = wp
wf :: (Wp wp) => Path wp -> Bool
wf (From wp) = True -- a path From x is always well formed
wf (GoTo p wp) =
((wp) `elem` (navigableFrom next)) && -- x is navigable from the endpoint of the path xs
(wf p) && -- the path xs is itself well formed
((nub waypoints) == waypoints) where -- only unique waypoints
waypoints = (path_to_waypoints p) ++ [wp]
next = (get_waypoint p)
-- Problem 2. Define a smart constructor `>:>` for `GoTo`
-- that returns `Nothing` if adding the given waypoint
-- to the given path would result in a non-well-formed path.
(>:>) :: (Wp wp) => Path wp -> wp -> Maybe (Path wp)
p >:> wp = if (wf new_path) then (Just new_path) else Nothing where
new_path = (GoTo p wp)
-- Problem 3. Write a function `extendPath` which returns
-- all possible ways of extending the given `Path` by appending
-- a single additional waypoint.
extendPath :: (Wp wp) => Path wp -> [Path wp]
extendPath p = catMaybes (map (\potential -> (p >:> potential)) potentials) where
potentials = navigableFrom (get_waypoint p)
-- Problem 4. Implement a function `findPaths` which returns
-- all possible ways of extending the given `Path` (by appending
-- any number of additional waypoints) into a path that ends
-- at the given target waypoint.
make_full_solution :: (Wp wp) => [Path wp] -> wp -> [Path wp]
make_full_solution paths target =
if ((length incomplete) <= 0) then solutions else solutions ++ (make_full_solution incomplete target) where
potential = concat (map (\p -> extendPath p) paths)
incomplete = filter (\p -> (head (path_to_waypoints p)) /= target) potential
solutions = filter (\p -> (head (path_to_waypoints p)) == target) potential
findPaths :: (Wp wp) => Path wp -> wp -> [Path wp]
findPaths p target = if ((get_waypoint p) == target) then [p] else make_full_solution [p] target
-- Efficiency mark 5: your solution should not spend time
-- expanding "useless" partial solutions.
--- PART 2: DISK MATTERS - ENCODE/DECODE
-- The floppy disk drive has no means of tracking the
-- angular position of the spinning magnetic disk.
-- This means that in principle, reading/writing can
-- begin at any position within the track, and the
-- user has no control over where the reading/writing
-- starts from.
-- For example, if you write [1,2,3,4,5,6] on a track of
-- capacity 6, it can happend that reading the track next
-- time will result in [5,6,1,2,3,4] or [2,3,4,5,6,1]. Note
-- however that the disk can spin only in one direction, so
-- you will never get a result like [6,5,4,3,2,1].
-- In this subproblem, you will come up with an encoding
-- scheme gets around the problem of the spinning disk.
-- represents a list of bytes encoded using the scheme
--
data Encoded = Encoded [Word8] deriving (Show, Eq)
unEncoded :: Encoded -> [Word8]
unEncoded (Encoded ws) = ws
-- Problem 5. Implement a function `rotate` which simulates
-- the effect of the spinning disk by rotating the given
-- list to the left by the given number of entries. E.g.
-- rotate 3 (Encoded [1,2,3,4]) = Encoded [4,1,2,3]
-- Hint: for negative n, you get to choose the behavior.
rotate_generic amount e = (b ++ a) where
(a, b) = splitAt (amount `rem` (max (length e) 1)) e
rotate :: Int -> Encoded -> Encoded
rotate amount (Encoded e) = Encoded (rotate_generic amount e)
-- Problem 6. Come up with an encoding scheme which gets
-- around the problem of the spinning disk. More formally,
-- implement a pair of functions, `encode` and `decode`, so
-- that:
--
-- 1. Decoding an encoded list of bytes results in the
-- original list, i.e. decode (encode bs) = Just bs.
-- 2. Decoding is rotationally invariant, i.e.
-- decode . rotate n . encode = Just for any positive n.
header :: [Word8]
header = [(fromIntegral 7), (fromIntegral 47)]
terminator :: [Word8]
terminator = [(fromIntegral 97), (fromIntegral 178)]
encoded_size :: [Word8] -> Int
encoded_size d = (2 * (length d)) + (length header) + (length terminator)
-- encode = 50, 100, 150 + data * 2 + null terminator
encode :: [Word8] -> Encoded
encode d = Encoded (header ++ d ++ d ++ terminator)
decode :: Encoded -> Maybe [Word8]
decode (Encoded e) =
if ((maybe_terminator /= terminator) || (maybe_header /= header) || (first /= second))
then decode (Encoded (rotate_generic 1 e))
else Just first where
maybe_header = take 2 e
maybe_terminator = reverse (take 2 (reverse e))
between = tail (tail (init (init e)))
(first, second) = splitAt ((length between) `div` 2) between
-- Efficiency mark: encoding a list of bytes with length
-- no more than 16 should result in an encoded list of
-- length no more than 37.
-- PART 3: FILE SYSTEM HIERARCHY
-- The rover's in-memory file system is organized into files and
-- directories. Each directory may contain other files and
-- directories inside it. Each file and directory is identified
-- by a unique `Word8`, its UID.
-- You can make the following assumptions about the file
-- system of the rover:
-- 1. The total size of all the files is no more than
-- 16kiB (16384 bytes).
-- 2. Every file is at most 3072 bytes long.
-- 3. There are at most 48 files and directories (but their
-- UIDs need not be in the range 0-47) altogether.
-- We have decided that one track on the disk will store the
-- contents of at most one file, i.e. that there will not be
-- any tracks which store multiple files.
-- However, since floppy tracks store only 2048 bytes, and a
-- single file may be longer than 2048 bytes, we will have to
-- come up with a way of storing a single file across multiple
-- tracks.
-- We will divide each file into a list of chunks, so that each
-- chunk is short enough to be stored in a single track. We will
-- assign each chunk its own unique track.
-- To reassemble a file, we have to read and decode each of its
-- chunks from the disk in order, then concatenate the results.
data Chunk =
Chunk TrackNo Encoded deriving (Show, Eq)
-- Problem 7. Write a stateful function `chunks` which,
-- when given the contents of a file, divides it into
-- a list of `Chunk`s.
-- The state `n` is a `TrackNo` between 0 and 255,
-- denoting the first track that is still available
-- to store data. E.g. if the state is 12, then
-- tracks 0-11 have already been used to store chunks,
-- but tracks 12-39 are still available. If all tracks
-- have been exhausted, signal the error by assiginng
-- the remaining chunks to track 40.
split_amount = (2048 `div` 2) - 4 -- encoding is data * 2 + 4
tn_max :: TrackNo
tn_max = fromIntegral 40
split_into_n :: Int -> [Word8] -> [[Word8]]
split_into_n n d = if (length d) <= n then [d] else [split] ++ concat [(split_into_n n rest)] where
(split, rest) = splitAt n d
chunks :: [Word8] -> State TrackNo [Chunk]
chunks d = do
tn <- get
put (min (tn + (fromIntegral (length cx))) tn_max)
return (map (\(Chunk tnn ee) -> Chunk (min (tn + tnn) tn_max) ee) cx) where
splits = split_into_n split_amount d
cx = map (\s -> Chunk (fromIntegral (fromJust (elemIndex s splits))) (encode s)) splits
-- The `FSH t` data type represents a file system hierarchy
-- in which each file is annotated with data of type `t`.
-- For example, `FSH [Word8]` can be used to represent the
-- entire file system, where each file is annotated with its
-- contents (a list of bytes), while the type `FSH ()` can
-- be used to represent just the hierarchical relationships
-- between the files and directories (i.e. which contains
-- which), but without any of the file data.
-- Problem 8. Write a lawful Functor instance for the FSH
-- type.
instance Functor FSH where
fmap f (File uid a) = (File uid (f a))
fmap f (Dir uid arr) = (Dir uid (map (fmap f) arr))
--instance Traversable FSH where
-- traverse f (File uid elem) = File uid <$> f elem
-- traverse f (Dir uid arr) = do
-- fx <- fmap (traverse f) arr
-- return $ Dir uid fx
--instance Traversable FSH where
-- traverse f (File uid elem) = File <$> uid <*> f elem
-- traverse f (Dir uid arr) = Dir <$> uid <*> (map (traverse f) arr)
--instance Traversable FSH where
-- traverse f (File uid elem) = File <$> uid <*> f elem
-- traverse f (Dir uid arr) = Dir <$> uid <*> (map (\x -> traverse f x) arr)
--instance Traversable FSH where
-- traverse f (File uid a) = (File uid (f a))
-- traverse f (Dir uid arr) = (Dir uid (map (traverse f) arr))
-- parseElems :: Traversal' Term Term
--traverse f (Dir uid arr) = (Dir (uid) (map (\x -> traverse f x) arr))
-- We will have to save the whole directory hierarchy to
-- disk before the rover is rebooted. So that we can reassemble
-- the hierarchy, we will use Track 0 to store a "header". This
-- header will represent a `FSH [TrackNo]` object, where each
-- file is annotated with the list of tracks that contain its
-- chunks.
-- The `mkHeader` function below will create this header
-- from a file system hierarchy where each file has been
-- annotated with a list of its chunks (assuming your
-- `Functor` instance is correct).
mkHeader :: FSH [Chunk] -> FSH [TrackNo]
mkHeader = fmap (map (\(Chunk n _) -> n))
-- Problem 9. Implement a function `assignTracks` which divides
-- all files in a hierarchy into chunks. Each chunk should have
-- be assigned its unique track number. Do not allocate track 0,
-- as that will be used to store the header.
-- Return `Nothing` if the given file system would not fit on
-- tracks 1-39 of a 40-track disk under your encoding.
-- HINT: You'll probably want to have a separate function
-- with return type `State TrackNo (FSH [Chunk])`.
state_lambda :: [Word8] -> State TrackNo [Chunk]
state_lambda d = do
tn <- get
let (cc, tt) = (runState (chunks d) tn)
put tt
return cc
--state_tracks :: FSH [Word8] -> State TrackNo (FSH [Chunk])
--state_tracks fshd = do
-- state <- get
--m <- (mapM (\x -> do
-- c <- chunks x
-- return c
-- ) fshd)
--return m
-- AHHHHHHHHHHHHHHHHHh
--state_tracks fshd = do
--let asdf = fmap state_lambda fshd
--return (fmap (state_lambda) fshd)
--return (fmap (\x -> evalState (state_lambda x) 1) fshd)
--return (fmap (\x -> evalState (state_lambda x) 1) fshd)
--return (fmap (\c -> do
-- let (cc, tt) = (runState (chunks c) tn)
-- return (head cc)) fshd)
--return (fmap (\c -> do
-- tn <- get
-- let cc = (evalState (chunks c) tn)
-- modify (+ fromIntegral (length cc))
-- return cc) fshd) where
--fmap' :: FSH [Word8]
--fmap' f (File uid a) = (File uid (f a))
--fmap' f (Dir uid arr) = (Dir uid (map (fmap f) arr))
--assignTracks :: FSH [Word8] -> Maybe (FSH [Chunk])
--assignTracks fshd =
-- if (tn >= tn_max) then Nothing else Just tracks where
-- (tracks, tn) = runState (state_tracks fshd) 1
-- PART 4 - DISK CONTROLLER
-- The disk controller supports four operations:
-- headForward - moves the read/write head forward by 1 track.
-- headBackward - moves the r/w head back toward zero by 1 track.
-- readTrack - reads 2048 consecutive bytes from the current track.
-- writeTrack - writes the given list of bytes to the current track.
-- In this problem, you will develop a program `saveFSH` that
-- uses this monad to save the entire file system onto the disk.
-- Problem 10. Write a program `headToTrack` that positions
-- the r/w head of the disk drive on the track with the given
-- number. If the number is larger than 39, position the head
-- on track 39.
head_forward_n :: (MonadFloppy m) => Word8 -> m ()
head_forward_n n = do
if (n > 0) then do
headForward
head_forward_n (n - 1)
else do
return ()
head_backward_n :: (MonadFloppy m) => Word8 -> m ()
head_backward_n n = do
if (n > 0) then do
headBackward
head_backward_n (n - 1)
else do
return ()
headToTrack :: (MonadFloppy m) => Word8 -> m ()
headToTrack p = do
head_backward_n 40
head_forward_n p
-- Problem 11. Write a program `saveChunk` which writes the
-- given chunk onto the appropriate track of the disk.
saveChunk :: (MonadFloppy m) => Chunk -> m ()
saveChunk (Chunk n (Encoded e)) = do
headToTrack n
writeTrack (replicate 2048 0)
writeTrack e
-- The function below calculates the header of the
-- given given `FSH [Chunk]`, and saves it to track 0
-- of the disk. Notice the use of the `toBytes` function.
saveHeader :: (MonadFloppy m) => FSH [Chunk] -> m ()
saveHeader fsh = do
headToTrack 0
writeTrack (replicate 2048 0)
writeTrack (unEncoded $ encode $ toBytes $ mkHeader fsh)
-- Problem 12. Implement a program `saveFSH` that attemps to assign
-- track to the given `fsh` using `assignTracks`. If the assignment
-- was unsuccessful, the program should return False.
-- If the assignment was successful, the program should write the
-- header to track 0 of the disk, then write all the assigned chunks
-- onto the appropriate tracks.
saveFSH :: (MonadFloppy m) => FSH [Word8] -> m Bool
saveFSH = error "'saveFSH' not implemented"
-- Implement a program `loadFSH` that is able to reload a file
-- system from disk. I.e. if `saveFSH fsh` returns `True`, then
-- (saveFSH fsh >> loadFSH) should return `Just fsh`.
-- HINT: To load the header, you might want to use the `fromBytes`
-- function.
loadFSH :: (MonadFloppy m) => m (Maybe (FSH [Word8]))
loadFSH = error "'loadFSH' not implemented"
|