Reworking day 16
[advent-of-code-22.git] / advent16 / Main.hs
1 -- Writeup at https://work.njae.me.uk/2022/12/17/advent-of-code-2022-day-16/
2
3 import Debug.Trace
4
5 import AoC
6 import Data.Text (Text)
7 import qualified Data.Text.IO as TIO
8 import Data.Attoparsec.Text hiding (take, D)
9 import Control.Applicative
10 import qualified Data.PQueue.Prio.Max as P
11 import qualified Data.Set as S
12 import qualified Data.Sequence as Q
13 import qualified Data.Map.Strict as M
14 import Data.Map.Strict ((!))
15 -- import Data.Sequence ((|>), Seq((:|>)), ViewR ((:>)))
16 import Data.Sequence ( (|>), Seq((:|>)) )
17 import Data.List
18 import Data.List.Split (chunksOf)
19 import Data.Ord
20 import Control.Monad.Reader
21 import Control.Lens hiding ((<|), (|>), (:>), (:<), indices)
22
23
24 type RoomID = String
25
26 data Tunnel = Tunnel { _tunnelTo :: RoomID, _tunnelLength :: Int}
27 deriving (Eq, Show, Ord)
28 makeLenses ''Tunnel
29
30 data Room = Room
31 { _flowRate :: Int
32 , _tunnels :: S.Set Tunnel
33 } deriving (Eq, Show, Ord)
34 makeLenses ''Room
35
36 type Cave = M.Map RoomID Room
37 data TimedCave = TimedCave { getCave :: Cave, getTimeLimit :: Int , getSortedRooms :: [RoomID]}
38
39 type CaveContext = Reader TimedCave
40
41 data SingleSearchState = SingleSearchState
42 { _currentRoom :: RoomID
43 , _currentTime :: Int
44 , _sOpenValves :: S.Set RoomID
45 } deriving (Eq, Show, Ord)
46 makeLenses ''SingleSearchState
47
48 data DoubleSearchState = DoubleSearchState
49 { _personRoom :: RoomID
50 , _personTime :: Int
51 , _elephantRoom :: RoomID
52 , _elephantTime :: Int
53 , _dOpenValves :: S.Set RoomID
54 } deriving (Eq, Show, Ord)
55 makeLenses ''DoubleSearchState
56
57 data Agendum s =
58 Agendum { _current :: s
59 , _trail :: Q.Seq s
60 , _trailBenefit :: Int
61 , _benefit :: Int
62 } deriving (Show, Eq, Ord)
63 makeLenses ''Agendum
64
65 type Agenda s = P.MaxPQueue Int (Agendum s)
66
67 -- state, total flowed so far
68 type ExploredStates s = S.Set (s, Int)
69
70
71 class (Eq s, Ord s, Show s) => SearchState s where
72 emptySearchState :: RoomID -> s
73 currentFlow :: s -> CaveContext Int
74 timeOf :: s -> Int
75 successors :: s -> CaveContext (Q.Seq s)
76 -- estimateBenefit :: s -> Int -> CaveContext Int
77 estimateBenefit :: s -> CaveContext Int
78
79 instance SearchState SingleSearchState where
80 emptySearchState startID = SingleSearchState
81 { _currentRoom = startID
82 , _currentTime = 0
83 , _sOpenValves = S.empty
84 }
85
86 currentFlow state =
87 do cave <- asks getCave
88 let valves = state ^. sOpenValves
89 let presentRooms = cave `M.restrictKeys` valves
90 return $ sumOf (folded . flowRate) presentRooms
91
92 timeOf state = state ^. currentTime
93
94 successors state =
95 do isFF <- isFullFlow state
96 -- cave <- asks getCave
97 timeLimit <- asks getTimeLimit
98 let here = state ^. currentRoom
99 let opened = state ^. sOpenValves
100 let now = state ^. currentTime
101 succs <- agentSuccessor now opened now here
102 let succStates = Q.fromList succs
103 if isFF || (Q.null succStates)
104 then return $ Q.singleton (state & currentTime .~ timeLimit)
105 else return succStates
106
107 estimateBenefit here =
108 do cave <- asks getCave
109 timeLimit <- asks getTimeLimit
110 let timeRemaining = timeLimit - (timeOf here)
111 cf <- currentFlow here
112 -- let closedValves = (cave `M.withoutKeys` (here ^. sOpenValves)) ^.. folded . flowRate
113 -- let sortedClosedValves = sortOn Down closedValves
114 sortedValves <- asks getSortedRooms
115 let opened = here ^. sOpenValves
116 let sortedClosedValves = [(cave ! v) ^. flowRate | v <- sortedValves, v `S.notMember` opened]
117 let otherValveFlows = sum $ zipWith (*) [timeRemaining, (timeRemaining - 2) .. 0] sortedClosedValves
118 return $ (cf * timeRemaining) + otherValveFlows
119
120
121 instance SearchState DoubleSearchState where
122 emptySearchState startID = DoubleSearchState
123 { _personRoom = startID
124 , _personTime = 0
125 , _elephantRoom = startID
126 , _elephantTime = 0
127 , _dOpenValves = S.empty
128 }
129
130 currentFlow state =
131 do cave <- asks getCave
132 let valves = S.toList $ state ^. dOpenValves
133 return $ sum $ fmap (\v -> (cave ! v) ^. flowRate) valves
134 -- let presentRooms = cave `M.restrictKeys` valves
135 -- return $ sumOf (folded . flowRate) presentRooms
136
137 timeOf state = min (state ^. personTime) (state ^. elephantTime)
138
139 successors state =
140 do isFF <- isFullFlow state
141 -- cave <- asks getCave
142 timeLimit <- asks getTimeLimit
143 let opened = state ^. dOpenValves
144 let pNow = state ^. personTime
145 let eNow = state ^. elephantTime
146 let now = min pNow eNow
147 let pHere = state ^. personRoom
148 let eHere = state ^. elephantRoom
149 pNexts <- agentSuccessor now opened pNow pHere
150 eNexts <- agentSuccessor now opened eNow eHere
151 let nexts = [ state & personRoom .~ (p ^. currentRoom)
152 & personTime .~ (p ^. currentTime)
153 & elephantRoom .~ (e ^. currentRoom)
154 & elephantTime .~ (e ^. currentTime)
155 & dOpenValves %~ (S.union (p ^. sOpenValves) . S.union (e ^. sOpenValves))
156 | p <- pNexts
157 , e <- eNexts
158 ]
159 let dedups = if pNow == eNow && pHere == eHere
160 then filter (\s -> (s ^. personRoom) < (s ^. elephantRoom)) nexts
161 -- else nexts
162 else filter (\s -> (s ^. personRoom) /= (s ^. elephantRoom)) nexts
163 -- let succStates = trace ("Succs: in " ++ (show state) ++ " out " ++ (show dedups)) (Q.fromList dedups)
164 let succStates = Q.fromList dedups
165 if isFF || (Q.null succStates)
166 then return $ Q.singleton (state & personTime .~ timeLimit & elephantTime .~ timeLimit)
167 else return succStates
168
169 estimateBenefit here =
170 do cave <- asks getCave
171 timeLimit <- asks getTimeLimit
172 let timeRemaining = timeLimit - (timeOf here)
173 cf <- currentFlow here
174 -- let closedValves = (cave `M.withoutKeys` (here ^. dOpenValves)) ^.. folded . flowRate
175 -- let sortedClosedValves = fmap sum $ chunksOf 2 $ {-# SCC estSort #-} sortOn Down closedValves
176 -- let sortedClosedValves = fmap sum $ chunksOf 2 $ reverse $ sort closedValves -- no significant improvement
177 sortedValves <- asks getSortedRooms
178 let opened = here ^. dOpenValves
179 let sortedClosedValves = fmap sum $ chunksOf 2 $ [(cave ! v) ^. flowRate | v <- sortedValves, v `S.notMember` opened]
180 let otherValveFlows = sum $ zipWith (*) [timeRemaining, (timeRemaining - 2) .. 0] sortedClosedValves
181 -- let otherValveFlows = timeRemaining * (sum closedValves) -- 8 minute runtime rather than 1:50
182 return $ (cf * timeRemaining) + otherValveFlows
183
184
185 main :: IO ()
186 main =
187 do dataFileName <- getDataFileName
188 text <- TIO.readFile dataFileName
189 let expandedCave = successfulParse text
190 -- print cave
191 -- print $ reachableFrom cave [Tunnel "AA" 0] S.empty []
192 -- print $ compress cave
193 let cave = compress expandedCave
194 print $ part1 cave
195 print $ part2 cave
196
197 -- part1 :: Cave -> Maybe (Agendum SingleSearchState)
198 -- part1 cave = runReader (searchCave "AA") (TimedCave cave 30)
199
200 -- part2 :: Cave -> Maybe (Agendum DoubleSearchState)
201 -- part2 cave = runReader (searchCave "AA") (TimedCave cave 26)
202
203 part1, part2 :: Cave -> Int
204 -- part1 :: Cave -> Int
205 part1 cave = maybe 0 _benefit result
206 where result = runReader (searchCave "AA") (TimedCave cave 30 sortedRooms) :: Maybe (Agendum SingleSearchState)
207 sortedRooms = sortOn (\r -> Down $ (cave ! r) ^. flowRate ) $ M.keys $ M.filter (\r -> r ^. flowRate > 0) cave
208 part2 cave = maybe 0 _benefit result
209 where result = runReader (searchCave "AA") (TimedCave cave 26 sortedRooms) :: Maybe (Agendum DoubleSearchState)
210 sortedRooms = sortOn (\r -> Down $ (cave ! r) ^. flowRate ) $ M.keys $ M.filter (\r -> r ^. flowRate > 0) cave
211 -- sortedRooms = sortOn (\r -> Down $ (cave ! r) ^. flowRate ) $ M.keys cave
212
213 searchCave :: SearchState s => String -> CaveContext (Maybe (Agendum s))
214 searchCave startRoom =
215 do agenda <- initAgenda startRoom
216 aStar agenda S.empty
217
218 initAgenda :: SearchState s => String -> CaveContext (Agenda s)
219 initAgenda startID =
220 do let startState = emptySearchState startID
221 b <- estimateBenefit startState
222 return $ P.singleton b Agendum { _current = startState, _trail = Q.empty, _trailBenefit = 0, _benefit = b}
223
224 aStar :: SearchState s => Agenda s -> ExploredStates s -> CaveContext (Maybe (Agendum s))
225 aStar agenda closed
226 -- | trace ("Peeping " ++ (show $ fst $ P.findMin agenda) ++ ": " ++ (show reached) ++ " <- " ++ (show $ toList $ Q.take 1 $ _trail $ currentAgendum) ++ " :: " ++ (show newAgenda)) False = undefined
227 -- | trace ("Peeping " ++ (show $ _current $ snd $ P.findMax agenda) ++ " : foundFlow " ++ (show $ _trailBenefit $ snd $ P.findMax agenda)) False = undefined
228 -- | trace ("Peeping " ++ (show $ _current $ snd $ P.findMax agenda) ++ " : foundFlow " ++ (show $ _trailBenefit $ snd $ P.findMax agenda) ++ " : trail " ++ (show $ _trail $ snd $ P.findMax agenda) ++ " : closed " ++ (show closed)) False = undefined
229 -- | trace ("Peeping " ++ (show $ P.findMax agenda)) False = undefined
230 | P.null agenda = return Nothing
231 | otherwise =
232 do let (_, currentAgendum) = P.findMax agenda
233 let reached = currentAgendum ^. current
234 nexts <- candidates currentAgendum closed
235 let newAgenda = foldl' (\q a -> P.insert (_benefit a) a q) (P.deleteMax agenda) nexts
236 -- let beamAgenda = P.fromDescList $ P.take 10000 newAgenda -- agenda beam width
237 -- let beamAgenda = P.fromDescList $ P.take 5000 newAgenda -- agenda beam width
238 -- let beamAgenda = P.fromDescList $ P.take 1000 newAgenda -- agenda beam width
239 reachedGoal <- isGoal currentAgendum
240 -- let cl = (reached, currentAgendum ^. trailBenefit, Q.length $ currentAgendum ^. trail)
241 let cl = (reached, currentAgendum ^. trailBenefit)
242 if reachedGoal
243 then return (Just currentAgendum)
244 else if (cl `S.member` closed)
245 then aStar (P.deleteMax agenda) closed
246 else aStar newAgenda (S.insert cl closed)
247 -- else aStar beamAgenda (S.insert cl closed)
248
249
250 candidates :: SearchState s => Agendum s -> ExploredStates s -> CaveContext (Q.Seq (Agendum s))
251 candidates agendum closed =
252 do let candidate = agendum ^. current
253 let previous = agendum ^. trail
254 let prevBenefit = agendum ^. trailBenefit
255 succs <- successors candidate
256 succAgs <- mapM (makeAgendum previous prevBenefit) succs
257 -- let nonloops = Q.filter (\s -> (s ^. current, s ^. trailBenefit, Q.length $ s ^. trail) `S.notMember` closed) succAgs
258 let nonloops = Q.filter (\s -> (s ^. current, s ^. trailBenefit) `S.notMember` closed) succAgs
259 return nonloops
260
261
262 agentSuccessor :: Int -> S.Set RoomID -> Int -> RoomID -> CaveContext [SingleSearchState]
263 agentSuccessor now opened aTime here
264 | aTime /= now = return [SingleSearchState { _currentRoom = here, _currentTime = aTime, _sOpenValves = opened }]
265 | otherwise =
266 do cave <- asks getCave
267 timeLimit <- asks getTimeLimit
268 let remaining = S.toList $ S.filter (\t -> (t ^. tunnelTo) `S.notMember` opened) ((cave ! here) ^. tunnels)
269 let moves = [ SingleSearchState
270 { _currentRoom = (t ^. tunnelTo)
271 , _currentTime = now + (t ^. tunnelLength)
272 , _sOpenValves = opened
273 }
274 | t <- remaining
275 , now + (t ^. tunnelLength) <= timeLimit
276 ]
277 let opens = if here `S.notMember` opened && (cave ! here) ^. flowRate > 0
278 then [SingleSearchState { _currentRoom = here, _currentTime = aTime + 1, _sOpenValves = S.insert here opened }]
279 else []
280 -- let nexts = moves ++ opens
281 let nexts = if null opens then moves else opens
282 let nexts' = if null nexts
283 then [ SingleSearchState
284 { _currentRoom = here
285 , _currentTime = timeLimit
286 , _sOpenValves = opened
287 } ]
288 else nexts
289 return nexts'
290
291 makeAgendum :: SearchState s => Q.Seq s -> Int -> s -> CaveContext (Agendum s)
292 makeAgendum previous prevBenefit newState =
293 do predicted <- estimateBenefit newState -- (Q.length previous)
294 -- cf <- currentFlow newState
295 oldFlow <- lastFlow previous (timeOf newState)
296 let newTrail = previous |> newState
297 let incurred = prevBenefit + oldFlow
298 return Agendum { _current = newState
299 , _trail = newTrail
300 , _trailBenefit = incurred
301 , _benefit = incurred + predicted
302 }
303
304 lastFlow :: SearchState s => Q.Seq s -> Int -> CaveContext Int
305 lastFlow Q.Empty _ = return 0
306 lastFlow (_ :|> previous) newTime =
307 do cf <- currentFlow previous
308 let dt = newTime - (timeOf previous)
309 return (cf * dt)
310
311 isGoal :: SearchState s => Agendum s -> CaveContext Bool
312 isGoal agendum =
313 do timeLimit <- asks getTimeLimit
314 let s = agendum ^. current
315 return $ (timeOf s) == timeLimit
316
317 isFullFlow :: SearchState s => s -> CaveContext Bool
318 isFullFlow state =
319 do cave <- asks getCave
320 cf <- currentFlow state
321 let ff = sumOf (folded . flowRate) cave
322 return (cf == ff)
323
324 compress :: Cave -> Cave
325 compress cave = M.mapWithKey (compressRoom cave) cave
326
327 compressRoom :: Cave -> RoomID -> Room -> Room
328 compressRoom cave here room = room & tunnels .~ t'
329 where t' = reachableFrom cave [Tunnel here 0] S.empty S.empty
330
331 reachableFrom :: Cave -> [Tunnel] -> S.Set RoomID -> S.Set Tunnel -> S.Set Tunnel
332 reachableFrom _ [] _ routes = routes
333 reachableFrom cave (tunnel@(Tunnel here len):boundary) found routes
334 | here `S.member` found = reachableFrom cave boundary found routes
335 | otherwise = reachableFrom cave (boundary ++ (S.toList legs)) (S.insert here found) routes'
336 where exits = (cave ! here) ^. tunnels
337 exits' = S.filter (\t -> (t ^. tunnelTo) `S.notMember` found) exits
338 legs = S.map (\t -> t & tunnelLength .~ (len + 1)) exits'
339 routes' = if (len == 0) || ((cave ! here) ^. flowRate) == 0
340 then routes
341 else S.insert tunnel routes
342
343
344 -- Parse the input file
345
346 caveP :: Parser Cave
347 valveP :: Parser (RoomID, Room)
348 roomP :: Parser Room
349 tunnelsP :: Parser (S.Set Tunnel)
350 tunnelTextP :: Parser Text
351
352 caveP = M.fromList <$> valveP `sepBy` endOfLine
353 valveP = (,) <$> ("Valve " *> (many1 letter)) <*> roomP
354 roomP = Room <$> (" has flow rate=" *> decimal) <*> (tunnelTextP *> tunnelsP)
355 -- where roomify v ts = Room {flowRate = v, tunnels = ts }
356 tunnelsP = (S.fromList . (fmap (flip Tunnel 1))) <$> (many1 letter) `sepBy` ", "
357 tunnelTextP = "; tunnels lead to valves " <|> "; tunnel leads to valve "
358
359 successfulParse :: Text -> Cave
360 successfulParse input =
361 case parseOnly caveP input of
362 Left _err -> M.empty -- TIO.putStr $ T.pack $ parseErrorPretty err
363 Right cave -> cave