diff options
Diffstat (limited to 'comp3141/hare/Hare.hs')
| -rw-r--r-- | comp3141/hare/Hare.hs | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/comp3141/hare/Hare.hs b/comp3141/hare/Hare.hs new file mode 100644 index 0000000..8da7287 --- /dev/null +++ b/comp3141/hare/Hare.hs @@ -0,0 +1,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" |
