never executed always true always false
    1 {-|
    2 Module:      Y2018.D04
    3 Description: Advent of Code Day 03 Solutions.
    4 License:     MIT
    5 Maintainer:  @tylerjl
    6 
    7 Solutions to the day 04 set of problems for <adventofcode.com>.
    8 -}
    9 module Y2018.D04
   10   ( laziestGuard
   11   , laziestMinute
   12   )
   13 where
   14 
   15 import Y2015.Util (regularParse, intParser)
   16 
   17 import qualified Data.Map.Strict as Map
   18 
   19 import Control.Applicative ((<|>))
   20 import Data.List           (foldl', maximumBy, sort)
   21 import Data.Ord            (comparing)
   22 import Text.Parsec.String  (Parser)
   23 import Text.Parsec.Char    (endOfLine)
   24 import Text.Parsec
   25     ( ParseError
   26     , many
   27     , optional
   28     , space
   29     , string
   30     )
   31 
   32 data Log = Log
   33   { timeStamp :: TimeStamp
   34   , entry     :: Entry
   35   } deriving (Eq, Ord, Show)
   36 
   37 data TimeStamp = TimeStamp
   38   { _year  :: Int
   39   , _month :: Int
   40   , _day   :: Int
   41   , _hour  :: Int
   42   , minute :: Int
   43   } deriving (Eq, Ord, Show)
   44 
   45 data Entry = StartShift Guard
   46            | Sleep
   47            | Wake
   48            deriving (Eq, Ord, Show)
   49 
   50 type Guard = Int
   51 
   52 type GuardHistory = (Maybe Guard, Map.Map Guard Shift)
   53 
   54 data Shift = Shift
   55   { minutesSlept :: Map.Map Int Int
   56   , lastChange   :: TimeStamp
   57   } deriving (Eq, Ord, Show)
   58 
   59 laziestMinute :: String -> Either ParseError Int
   60 laziestMinute input = case parseLog input of
   61   Left e -> Left e
   62   Right logs -> Right $ logFilter maximum logs
   63 
   64 laziestGuard :: String -> Either ParseError Int
   65 laziestGuard input = case parseLog input of
   66   Left e -> Left e
   67   Right logs -> Right $ logFilter (Map.foldl (+) 0) logs
   68 
   69 logFilter :: Ord a => (Map.Map Int Int -> a) -> [Log] -> Guard
   70 logFilter f logs =
   71     let sleepiestMinute =
   72           fst
   73           $ maximumBy (\m c -> compare (snd m) (snd c))
   74           $ Map.toList
   75           $ minutesSlept
   76           $ snd guard
   77         guardID = fst guard
   78         guard =
   79           maximumBy (comparing (f . minutesSlept . snd))
   80           $ Map.toList
   81           $ snd
   82           $ foldl' recordLog (Nothing, Map.empty)
   83           $ sort logs
   84     in guardID * sleepiestMinute
   85 
   86 recordLog :: GuardHistory -> Log -> GuardHistory
   87 recordLog (_current, h) (Log { timeStamp = ts, entry = (StartShift g) }) =
   88   (Just g, Map.insertWith shiftChange g toShift h)
   89   where toShift =
   90           Shift
   91             { minutesSlept = Map.fromList $ zip [0 .. 59] $ repeat 0
   92             , lastChange   = ts
   93             }
   94         shiftChange _newShift oldShift =
   95           oldShift { lastChange = ts }
   96 recordLog (Just current, h) (Log { timeStamp = ts@(TimeStamp { minute = m }), entry = (Wake) }) =
   97   (Just current, Map.adjust transition current h)
   98   where transition oldShift@(Shift { lastChange = (TimeStamp { minute = m' }), minutesSlept = minutes }) =
   99           oldShift
  100             { lastChange = ts
  101             , minutesSlept = Map.unionWith (+) minutes $ Map.fromList $ zip [m' .. (m - 1)] $ repeat 1
  102             }
  103 recordLog (Just current, h) (Log { timeStamp = ts, entry = (Sleep) }) =
  104   (Just current, Map.adjust transition current h)
  105   where transition oldShift = oldShift { lastChange = ts }
  106 recordLog gh@(Nothing, _) _ = gh
  107 
  108 -- Parsing
  109 
  110 parseLog :: String
  111          -> Either ParseError [Log]
  112 parseLog = regularParse logParser
  113 
  114 logParser :: Parser [Log]
  115 logParser = many (parseRawLog <* optional endOfLine)
  116 
  117 parseRawLog :: Parser Log
  118 parseRawLog = Log <$> (parseTimeStamp <* space) <*> parseEntry
  119 
  120 parseTimeStamp :: Parser TimeStamp
  121 parseTimeStamp = TimeStamp <$ string "[" <*> intParser <* string "-"
  122                           <*> intParser  <*  string "-"
  123                           <*> intParser  <*  space
  124                           <*> intParser  <*  string ":"
  125                           <*> intParser  <*  string "]"
  126 
  127 parseEntry :: Parser Entry
  128 parseEntry = StartShift <$ string "Guard #" <*> intParser <* string " begins shift"
  129          <|> Sleep      <$ string "falls asleep"
  130          <|> Wake       <$ string "wakes up"