debugMinor
CIS 194 Homework 2: Parsing an error log
Viewed 0 times
homeworkerrorlogcisparsing194
Problem
Here is another reference solution, that I discovered after writing my own:
https://insight.io/github.com/beloglazov/haskell-course/blob/HEAD/hw2.hs
I am following this lecture course as a complete beginner to Haskell. As there is no one to check my solutions to the homework assignments, I am posting here.
Until now, the lecture notes include: boolean logic, functions, tuples, lists, algebraic types, pattern matching, Prelude, case expressions, recursive data types.
The task is to parse this (and similar) error log files:
The meaning of those lines can be glimpsed in the provided together with the homework file Log.hs:
```
module Log where
import Control.Applicative
data MessageType = Info
| Warning
| Error Int
deriving (Show, Eq)
type TimeStamp = Int
data LogMessage = LogMessage MessageType TimeStamp String
| Unknown String
deriving (Show, Eq)
data MessageTree = Leaf
| Node MessageTree LogMessage MessageTree
deriving (Show, Eq)
-- | @testParse p n f@ tests the log file parser @p@ by running it
-- on the first @n@ lines of file @f@.
testParse :: (String -> [LogMessage])
-> Int
-> FilePath
-> IO [LogMessage]
testParse parse n file = take n . parse readFile file
-- | @testWhatWentWrong p w f@ tests the log file parser @p@ and
-- warning message extractor @w@ by running them on the log file
-- @f@.
testWhatWentWrong :: (String -> [LogMessage])
-> ([LogMessage] -> [String])
-> FilePath
-> IO [String]
testWhatWentWrong parse whatWentWrong file
= whatWentW
https://insight.io/github.com/beloglazov/haskell-course/blob/HEAD/hw2.hs
I am following this lecture course as a complete beginner to Haskell. As there is no one to check my solutions to the homework assignments, I am posting here.
Until now, the lecture notes include: boolean logic, functions, tuples, lists, algebraic types, pattern matching, Prelude, case expressions, recursive data types.
The task is to parse this (and similar) error log files:
I 6 Completed armadillo processing
I 1 Nothing to report
I 4 Everything normal
I 11 Initiating self-destruct sequence
E 70 3 Way too many pickles
E 65 8 Bad pickle-flange interaction detected
W 5 Flange is due for a check-up
I 7 Out for lunch, back in two time steps
E 20 2 Too many pickles
I 9 Back from lunch
E 99 10 Flange failed!The meaning of those lines can be glimpsed in the provided together with the homework file Log.hs:
```
module Log where
import Control.Applicative
data MessageType = Info
| Warning
| Error Int
deriving (Show, Eq)
type TimeStamp = Int
data LogMessage = LogMessage MessageType TimeStamp String
| Unknown String
deriving (Show, Eq)
data MessageTree = Leaf
| Node MessageTree LogMessage MessageTree
deriving (Show, Eq)
-- | @testParse p n f@ tests the log file parser @p@ by running it
-- on the first @n@ lines of file @f@.
testParse :: (String -> [LogMessage])
-> Int
-> FilePath
-> IO [LogMessage]
testParse parse n file = take n . parse readFile file
-- | @testWhatWentWrong p w f@ tests the log file parser @p@ and
-- warning message extractor @w@ by running them on the log file
-- @f@.
testWhatWentWrong :: (String -> [LogMessage])
-> ([LogMessage] -> [String])
-> FilePath
-> IO [String]
testWhatWentWrong parse whatWentWrong file
= whatWentW
Solution
Simplifications for the pattern usage you currently have:
The pattern in
That is the first thing you may want to adjust. As it now stands you're doing a lot of work with "splitting and reassembling" strings.
In the spirit of avoiding unnecessary work, you should only do that once.
Now assuming your
Note that
This allows you to completely drop the helpers
Additional minor simplifications, spoilers hide the full solution ;)
In this case, the fold is "right-associative", so the whole thing can be reformulated with
Here's a bit of an explanation why this works:
Let's write this a bit more ... verbosely:
Note that there is a huge bug in how
Sidenote here: you can simplify
If you weren't probably forced to use a tree for sorting
Similarly,
Note that this particular piece is untested
An additional simplification is noticing that
The pattern in
parseMessage can be extended so you don't need to explicitly use dropWords. That only works if you change the signature to something based on words instead of the whole line, though.That is the first thing you may want to adjust. As it now stands you're doing a lot of work with "splitting and reassembling" strings.
In the spirit of avoiding unnecessary work, you should only do that once.
Now assuming your
parseMessage were to work off a "splitted" string, you could instead have:parseMessage :: [String] -> L.LogMessage
parseMessage "E":e:t:m = L.LogMessage (L.Error (read e)) (read t) (unwords m)
parseMessage "W":t:m = L.LogMessage L.Warning (read t) (unwords m)
parseMessage "I":t:m = L.LogMessage L.Info (read t) (unwords m)
parseMessage p = L.Unknown (unwords p)
Note that
unwords already correctly handles the case of p = []This allows you to completely drop the helpers
dropWords and wordsToNum.Additional minor simplifications, spoilers hide the full solution ;)
parsecould be written as a function composition to avoid specifying the parameters.
parse = (map parseMessage) . linesbuildis commonly known in functional programming as afold. It collapses a list into a single element.
In this case, the fold is "right-associative", so the whole thing can be reformulated with
foldr:build = foldr insert LeafHere's a bit of an explanation why this works:
Let's write this a bit more ... verbosely:
build ms = foldr insert Leaf ms. What basically happens here is that you give a neutral or starter element (Leaf) and insert is repeatedly called in the following way:(m1 insert (m2 insert (... (mn insert Leaf))))Note that there is a huge bug in how
insert works. As it stands, insert will only ever return a tree with a single Node in it. To understand why you need to reevaluate what insert does and how mutability works in haskell (hint: it doesn't).Sidenote here: you can simplify
insert a bit with an if .. then .. else ... You should call insert a few times and see what happens with the LogMessage that has the timestamp t2.If you weren't probably forced to use a tree for sorting
sort would be much easier to write in terms of Data.List.sortBy.Similarly,
filterList also already exists in the form of filter:filterList = filter (\(L.LogMessage (Error e) _ _) -> e >= 50)Note that this particular piece is untested
An additional simplification is noticing that
toString is just map showContext
StackExchange Code Review Q#160987, answer score: 4
Revisions (0)
No revisions yet.