-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay4.hs
153 lines (129 loc) · 4.37 KB
/
Day4.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
module Day4
( part1
, part2
) where
import Control.Monad (void)
import Data.Bifunctor (second)
import Data.Char (isDigit)
import Data.Either (fromRight)
import Data.IntMap (IntMap, fromList)
import Data.List (group, groupBy, maximumBy, sort, sortBy)
import Data.List.Split (chunksOf)
import Helpers.Parsers (Parser)
import Text.Megaparsec (eof, many, optional, parse, takeWhile1P,
try, (<|>))
import Text.Megaparsec.Char (char, eol, string)
data Date =
Date Year Month Day
deriving (Show, Eq)
type Year = Int
type Month = Int
type Day = Int
data Time =
Time Hour Minute
deriving (Show, Eq)
type Hour = Int
type Minute = Int
data Activity
= ID Int
| Sleep
| Wake
deriving (Show, Eq, Ord)
data Stamp =
Stamp Time Activity
deriving (Show, Eq, Ord)
instance Ord Time where
compare (Time h1 m1) (Time h2 m2) = compare h1 h2 `mappend` compare m1 m2
instance Ord Date where
compare (Date y1 m1 d1) (Date y2 m2 d2) =
compare y1 y2 `mappend` compare m1 m2 `mappend` compare d1 d2
next :: Date -> Date
next (Date y m d)
| m == 12 && d == 31 = Date (y + 1) 1 1
| d == 31 && (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10) =
Date y (m + 1) 1
| d == 31 = error ("31st day of " ++ show m)
| d == 30 && (m == 4 || m == 6 || m == 9 || m == 11) = Date y (m + 1) 1
| d == 29 && m == 2 && mod y 4 == 0 = Date y (m + 1) 1
| d == 28 && m == 2 && mod y 4 /= 0 = Date y (m + 1) 1
| d == 30 && m == 2 = error "February does not have 30 days"
| d == 29 && m == 2 = error ("February " ++ show y ++ " is not a leap year.")
| otherwise = Date y m (d + 1)
parser :: Parser [(Date, Stamp)]
parser = many parseLine <* eof
parseLine :: Parser (Date, Stamp)
parseLine = do
void . char $ '['
year <- read <$> takeWhile1P Nothing isDigit
void . char $ '-'
month <- read <$> takeWhile1P Nothing isDigit
void . char $ '-'
day <- read <$> takeWhile1P Nothing isDigit
void . char $ ' '
hour <- read <$> takeWhile1P Nothing isDigit
void . char $ ':'
minute <- read <$> takeWhile1P Nothing isDigit
void . string $ "] "
activity <- try parseShift <|> try parseSleep <|> parseWake
void . optional $ eol
let date = Date year month day
result
| hour == 23 = (next date, Stamp (Time 0 (-1)) activity)
| otherwise = (date, Stamp (Time hour minute) activity)
return result
parseShift :: Parser Activity
parseShift = do
void . string $ "Guard #"
iD <- read <$> takeWhile1P Nothing isDigit
void . string $ " begins shift"
return (ID iD)
parseSleep :: Parser Activity
parseSleep = do
void . string $ "falls asleep"
return Sleep
parseWake :: Parser Activity
parseWake = do
void . string $ "wakes up"
return Wake
makeSleepMap :: [(Date, Stamp)] -> [(Int, [[Int]])]
makeSleepMap =
map (foldr (\(a, b) (_, d) -> (a, b : d)) (0, [])) .
groupBy (\(a, _) (b, _) -> a == b) .
sortBy (\(a, _) (b, _) -> compare a b) .
map
(makeGuardDay .
sortBy (\(Stamp t1 _) (Stamp t2 _) -> compare t1 t2) . map snd) .
groupBy (\(a, _) (b, _) -> a == b) . sortBy (\(a, _) (b, _) -> compare a b)
makeGuardDay :: [Stamp] -> (Int, [Int])
makeGuardDay day = (iD, dets)
where
(Stamp _ (ID iD)) = head day
periods = chunksOf 2 . tail $ day
dets =
concatMap
(\[Stamp (Time _ m1) Sleep, Stamp (Time _ m2) Wake] -> [m1 .. (m2 - 1)])
periods
maxSleep :: [(Int, [[Int]])] -> Int
maxSleep records = id * minute
where
(id, sleeps) =
maximumBy
(\(_, a) (_, b) -> compare (sum . map length $ a) (sum . map length $ b))
records
minute =
head .
maximumBy (\a b -> compare (length a) (length b)) . group . sort . concat $
sleeps
bestMin :: [(Int, [[Int]])] -> Int
bestMin records = id * minute
where
sorted =
map (second (maximumBy (\a b -> compare (length a) (length b)))) .
filter (\(_, b) -> not (null b)) . map (second (group . sort . concat)) $
records
(id, minute:_) =
maximumBy (\(_, a) (_, b) -> compare (length a) (length b)) sorted
part1 :: Bool -> String -> String
part1 _ = show . maxSleep . makeSleepMap . fromRight [] . parse parser ""
part2 :: Bool -> String -> String
part2 _ = show . bestMin . makeSleepMap . fromRight [] . parse parser ""