Compile and run with
$ ./main input.txt
Alex: 42 miles @ 34 mph
Dan: 39 miles @ 47 mph
Bob: 0 miles
Driver Dan | |
Driver Alex | |
Driver Bob | |
Trip Dan 07:15 07:45 17.3 | |
Trip Dan 06:12 06:32 21.8 | |
Trip Alex 12:01 13:16 42.0 |
module Main where | |
import Data.List | |
import Data.Map (Map) | |
import qualified Data.Map as Map | |
import Data.Maybe (listToMaybe, maybe) | |
import Data.Ord (comparing) | |
import System.Environment (getArgs) | |
data ReportEntry = ReportEntry | |
{ name :: String | |
, miles :: Int | |
, mph :: Maybe Int | |
} | |
type Report = [ReportEntry] | |
data CommandEntry | |
= DriverEntry String | |
| TripEntry { driverName :: String | |
, start :: String | |
, end :: String | |
, tripMiles :: String } | |
type CommandEntryMap = Map String (Int, Float) | |
parseLine :: [String] -> CommandEntry | |
parseLine ("Driver":name:_) = DriverEntry name | |
parseLine ("Trip":name:start:end:miles:_) = TripEntry name start end miles | |
parse :: String -> [CommandEntry] | |
parse file = parseLine . words <$> lines file | |
generateReport :: [CommandEntry] -> Report | |
generateReport = | |
(reverse . sortBy (comparing name)) <$> | |
((map . toRepEntry . getTrips) <*> getNames) | |
formatRepEntry :: ReportEntry -> String | |
formatRepEntry (ReportEntry name miles mph) = | |
name ++ | |
": " ++ | |
show miles ++ " miles" ++ maybe "" ((" @ " ++) . (++ " mph") . show) mph | |
toRepEntry :: CommandEntryMap -> String -> ReportEntry | |
toRepEntry trips name = toRepEntry' $ Map.lookup name trips | |
where | |
toRepEntry' :: Maybe (Int, Float) -> ReportEntry | |
toRepEntry' (Just (minutes, miles)) = | |
ReportEntry | |
name | |
(round miles) | |
(Just $ round $ miles / (fromIntegral minutes / 60)) | |
toRepEntry' Nothing = ReportEntry name 0 Nothing | |
getNames :: [CommandEntry] -> [String] | |
getNames [] = [] | |
getNames (DriverEntry name:xs) = name : getNames xs | |
getNames (_:xs) = getNames xs | |
getTrips :: [CommandEntry] -> CommandEntryMap | |
getTrips [] = Map.empty | |
getTrips ((TripEntry name start end miles):xs) = | |
Map.insertWith | |
(\(t, m) (t', m') -> (t + t', m + m')) | |
name | |
(differenceInMinutes start end, (read miles :: Float)) | |
(getTrips xs) | |
getTrips (_:xs) = getTrips xs | |
differenceInMinutes :: String -> String -> Int | |
differenceInMinutes start end = toMinutes end - toMinutes start | |
toMinutes :: String -> Int | |
toMinutes (h:h':_:m:m':_) = | |
(read (h : h' : []) :: Int) * 60 + (read (m : m' : []) :: Int) | |
main :: IO () | |
main = | |
listToMaybe <$> getArgs >>= \file -> | |
case file of | |
Just file' -> do | |
entries <- parse <$> readFile file' | |
mapM_ putStrLn (map formatRepEntry $ generateReport entries) | |
Nothing -> putStrLn "File does not exist" |