diff --git a/4/4.hs b/4/4.hs new file mode 100644 index 0000000..ac4a71d --- /dev/null +++ b/4/4.hs @@ -0,0 +1,85 @@ +{-# LANGUAGE Strict #-} +module Main where + +import Control.Applicative (some) +import Data.Function (on) +import Data.List (sort, foldl', maximumBy, groupBy, sortOn) +import Data.List.Split (split, dropInitBlank, keepDelimsL, whenElt, chunksOf) +import qualified Data.Map as M +import Data.Time (LocalTime, diffUTCTime, localTimeToUTC, utc, todMin, localTimeOfDay) +import Data.Time.Clock (NominalDiffTime) +import Data.Time.Format (parseTimeOrError, defaultTimeLocale) +import Text.Parsec + +type GuardId = Int +data Action = BeginShift GuardId + | FallAsleep + | WakeUp + deriving (Show, Eq) +data Record = Record { recTime :: LocalTime, recAction :: Action } + deriving (Show, Eq) +type Round = [Record] + +shiftBeginning :: Record -> Bool +shiftBeginning (Record _ (BeginShift _)) = True +shiftBeginning _ = False + +instance Ord Record where + compare = compare `on` recTime + +parseTime :: String -> LocalTime +parseTime = parseTimeOrError False defaultTimeLocale "%F %R" + +diffLocalTime :: LocalTime -> LocalTime -> NominalDiffTime +diffLocalTime a b = diffUTCTime (localTimeToUTC utc a) (localTimeToUTC utc b) + +inputP :: Parsec String () Record +inputP = Record <$> (tsP <* space) <*> (bsP <|> wP <|> aP) + where + intP = read <$> some digit + tsP = parseTime <$> (char '[' *> some (noneOf "]") <* char ']') + + bsP = BeginShift + <$> (string "Guard" *> space *> char '#' *> intP <* space <* string "begins shift") + + wP = WakeUp <$ string "wakes up" + aP = FallAsleep <$ string "falls asleep" + +readInput :: [String] -> [Record] +readInput ls = case traverse (parse inputP "") ls of + Left e -> error (show e) + Right rs -> sort rs + +splitRounds :: [Record] -> [[Record]] +splitRounds = split $ dropInitBlank $ keepDelimsL $ whenElt shiftBeginning + +guardID :: Round -> GuardId +guardID ~(Record _ (BeginShift gID) : _) = gID + +summarizeRounds :: [Round] -> (GuardId, Int, (Int, Int)) +summarizeRounds rounds = + (guardID $ head rounds, + totalSleep rounds, + case M.assocs minMap of + [] -> (-1, 0) + xs -> maximumBy (compare `on` snd) xs) + where + getMin = todMin . localTimeOfDay . recTime + + totalSleep = sum . map (sum . map (\[s,e] -> getMin e - getMin s) . chunksOf 2 . tail) + minMap = + foldl' (\a r -> + foldl' (\a' [s,e] -> + foldl' (\a'' m -> M.insertWith (+) m 1 a'') a' [getMin s .. (getMin e - 1)]) + a (chunksOf 2 . tail $ r)) + M.empty rounds + +mostAsleep = maximumBy (compare `on` (\(_,t,_) -> t)) +mostAsleepOnSameMinute = maximumBy (compare `on` (\(_,_,(_,m)) -> m)) + +main = do + rounds <- splitRounds . readInput . lines <$> getContents + let summary = map summarizeRounds . groupBy ((==) `on` guardID) . sortOn guardID $ rounds + putStrLn $ "Summary = " ++ show summary + putStrLn $ "Most asleep = " ++ show (mostAsleep summary) + putStrLn $ "Most asleep on same minute = " ++ show (mostAsleepOnSameMinute summary) diff --git a/4/Setup.hs b/4/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/4/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/4/stack.yaml b/4/stack.yaml new file mode 100644 index 0000000..2864202 --- /dev/null +++ b/4/stack.yaml @@ -0,0 +1,4 @@ +resolver: lts-12.21 + +packages: +- . diff --git a/4/x4.cabal b/4/x4.cabal new file mode 100644 index 0000000..ac48f50 --- /dev/null +++ b/4/x4.cabal @@ -0,0 +1,27 @@ +-- Initial x4.cabal generated by cabal init. For further documentation, +-- see http://haskell.org/cabal/users-guide/ + +name: x4 +version: 0.1.0.0 +-- synopsis: +-- description: +license: BSD3 +author: Abhinav Sarkar +maintainer: abhinav@abhinavsarkar.net +-- copyright: +-- category: +build-type: Simple +cabal-version: >=1.10 + +executable x4 + main-is: 4.hs + -- other-modules: + -- other-extensions: + build-depends: base >=4.11 && <4.12 + ,split + ,containers + ,time + ,parsec + ghc-options: -O2 + -- hs-source-dirs: + default-language: Haskell2010