1 -- Writeup at https://work.njae.me.uk/2022/12/17/advent-of-code-2022-day-16/
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 ((|>))
17 import Data.List.Split (chunksOf)
19 import Control.Monad.Reader
20 import Control.Lens hiding ((<|), (|>), (:>), (:<), indices)
22 -- pattern Empty <- (Q.viewl -> Q.EmptyL) where Empty = Q.empty
23 -- pattern x :< xs <- (Q.viewl -> x Q.:< xs) where (:<) = (Q.<|)
24 -- pattern xs :> x <- (Q.viewr -> xs Q.:> x) where (:>) = (Q.|>)
30 , _tunnels :: [RoomID]
31 } deriving (Eq, Show, Ord)
34 type Cave = M.Map RoomID Room
35 data TimedCave = TimedCave { getCave :: Cave, getTimeLimit :: Int}
37 type CaveContext = Reader TimedCave
39 data SingleSearchState = SingleSearchState
40 { _currentRoom :: RoomID
41 , _sOpenValves :: S.Set RoomID
42 } deriving (Eq, Show, Ord)
43 makeLenses ''SingleSearchState
45 data DoubleSearchState = DoubleSearchState
46 { _personRoom :: RoomID
47 , _elephantRoom :: RoomID
48 , _dOpenValves :: S.Set RoomID
49 } deriving (Eq, Show, Ord)
50 makeLenses ''DoubleSearchState
53 Agendum { _current :: s
55 , _trailBenefit :: Int
57 } deriving (Show, Eq, Ord)
60 type Agenda s = P.MaxPQueue Int (Agendum s)
62 type ExploredStates s = S.Set (s, Int, Int)
65 class (Eq s, Ord s, Show s) => SearchState s where
66 emptySearchState :: RoomID -> s
67 currentFlow :: s -> CaveContext Int
68 successors :: s -> CaveContext (Q.Seq s)
69 estimateBenefit :: s -> Int -> CaveContext Int
71 instance SearchState SingleSearchState where
72 emptySearchState startID = SingleSearchState { _currentRoom = startID, _sOpenValves = S.empty }
75 do cave <- asks getCave
76 let valves = state ^. sOpenValves
77 let presentRooms = cave `M.restrictKeys` valves
78 return $ sumOf (folded . flowRate) presentRooms
81 do isFF <- isFullFlow state
82 let here = state ^. currentRoom
83 let opened = state ^. sOpenValves
84 succPairs <- personSuccessor here opened
93 then return $ Q.singleton state
94 else return $ Q.fromList succStates
96 estimateBenefit here timeElapsed =
97 do cave <- asks getCave
98 timeLimit <- asks getTimeLimit
99 let timeRemaining = timeLimit - (timeElapsed + 2)
100 cf <- currentFlow here
101 let closedValves = (cave `M.withoutKeys` (here ^. sOpenValves)) ^.. folded . flowRate
102 let sortedClosedValves = sortOn Down closedValves
103 let otherValveFlows = sum $ zipWith (*) [timeRemaining, (timeRemaining - 2) .. 0] sortedClosedValves
104 return $ (cf * timeRemaining) + otherValveFlows
107 instance SearchState DoubleSearchState where
108 emptySearchState startID = DoubleSearchState
109 { _personRoom = startID
110 , _elephantRoom = startID
111 , _dOpenValves = S.empty
115 do cave <- asks getCave
116 let valves = state ^. dOpenValves
117 let presentRooms = cave `M.restrictKeys` valves
118 return $ sumOf (folded . flowRate) presentRooms
121 do isFF <- isFullFlow state
122 let pHere = state ^. personRoom
123 let eHere = state ^. elephantRoom
124 let opened = state ^. dOpenValves
125 pSuccPairs <- personSuccessor pHere opened
126 eSuccPairs <- personSuccessor eHere opened
131 , _dOpenValves = S.union po eo
133 | (p, po) <- pSuccPairs
134 , (e, eo) <- eSuccPairs
137 then return $ Q.singleton state
138 else return $ Q.fromList succStates
140 estimateBenefit here timeElapsed =
141 do cave <- asks getCave
142 timeLimit <- asks getTimeLimit
143 let timeRemaining = timeLimit - (timeElapsed + 2)
144 cf <- currentFlow here
145 let closedValves = (cave `M.withoutKeys` (here ^. dOpenValves)) ^.. folded . flowRate
146 let sortedClosedValves = fmap sum $ chunksOf 2 $ sortOn Down closedValves
147 let otherValveFlows = sum $ zipWith (*) [timeRemaining, (timeRemaining - 2) .. 0] sortedClosedValves
148 return $ (cf * timeRemaining) + otherValveFlows
153 do dataFileName <- getDataFileName
154 text <- TIO.readFile dataFileName
155 let cave = successfulParse text
160 -- part1 :: Cave -> Maybe (Agendum SingleSearchState)
161 -- part1 cave = runReader (searchCave "AA") (TimedCave cave 30)
163 -- part2 :: Cave -> Maybe (Agendum DoubleSearchState)
164 -- part2 cave = runReader (searchCave "AA") (TimedCave cave 26)
166 part1, part2 :: Cave -> Int
167 part1 cave = maybe 0 _benefit result
168 where result = runReader (searchCave "AA") (TimedCave cave 30) :: Maybe (Agendum SingleSearchState)
169 part2 cave = maybe 0 _benefit result
170 where result = runReader (searchCave "AA") (TimedCave cave 26) :: Maybe (Agendum DoubleSearchState)
172 searchCave :: SearchState s => String -> CaveContext (Maybe (Agendum s))
173 searchCave startRoom =
174 do agenda <- initAgenda startRoom
177 initAgenda :: SearchState s => String -> CaveContext (Agenda s)
179 do let startState = emptySearchState startID
180 b <- estimateBenefit startState 0
181 return $ P.singleton b Agendum { _current = startState, _trail = Q.empty, _trailBenefit = 0, _benefit = b}
183 aStar :: SearchState s => Agenda s -> ExploredStates s -> CaveContext (Maybe (Agendum s))
185 -- | trace ("Peeping " ++ (show $ fst $ P.findMin agenda) ++ ": " ++ (show reached) ++ " <- " ++ (show $ toList $ Q.take 1 $ _trail $ currentAgendum) ++ " :: " ++ (show newAgenda)) False = undefined
186 -- | trace ("Peeping " ++ (show $ _current $ snd $ P.findMax agenda) ++ " : len " ++ (show $ Q.length $ _trail $ snd $ P.findMax agenda)) False = undefined
187 | P.null agenda = return Nothing
189 do let (_, currentAgendum) = P.findMax agenda
190 let reached = currentAgendum ^. current
191 nexts <- candidates currentAgendum closed
192 let newAgenda = foldl' (\q a -> P.insert (_benefit a) a q) (P.deleteMax agenda) nexts
193 -- let beamAgenda = P.fromDescList $ P.take 10000 newAgenda -- agenda beam width
194 -- let beamAgenda = P.fromDescList $ P.take 5000 newAgenda -- agenda beam width
195 reachedGoal <- isGoal currentAgendum
196 let cl = (reached, currentAgendum ^. trailBenefit, Q.length $ currentAgendum ^. trail)
198 then return (Just currentAgendum)
199 else if (cl `S.member` closed)
200 then aStar (P.deleteMax agenda) closed
201 else aStar newAgenda (S.insert cl closed)
202 -- else aStar beamAgenda (S.insert cl closed)
205 candidates :: SearchState s => Agendum s -> ExploredStates s -> CaveContext (Q.Seq (Agendum s))
206 candidates agendum closed =
207 do let candidate = agendum ^. current
208 let previous = agendum ^. trail
209 let prevBenefit = agendum ^. trailBenefit
210 succs <- successors candidate
211 succAgs <- mapM (makeAgendum previous prevBenefit) succs
212 let nonloops = Q.filter (\s -> (s ^. current, s ^. trailBenefit, Q.length $ s ^. trail) `S.notMember` closed) succAgs
215 personSuccessor, openValveSuccessor, walkSuccessor :: RoomID -> S.Set RoomID -> CaveContext [(RoomID, S.Set RoomID)]
216 personSuccessor here opened =
217 do ovs <- openValveSuccessor here opened
218 ws <- walkSuccessor here opened
221 openValveSuccessor here opened
222 | here `S.member` opened = return []
223 | otherwise = return [(here, S.insert here opened)]
225 walkSuccessor here opened =
226 do cave <- asks getCave
227 let neighbours = (cave ! here) ^. tunnels
228 return [(n, opened) | n <- neighbours]
230 makeAgendum :: SearchState s => Q.Seq s -> Int -> s -> CaveContext (Agendum s)
231 makeAgendum previous prevBenefit newState =
232 do predicted <- estimateBenefit newState (Q.length previous)
233 cf <- currentFlow newState
234 let newTrail = previous |> newState
235 let incurred = prevBenefit + cf
236 return Agendum { _current = newState
238 , _trailBenefit = incurred
239 , _benefit = incurred + predicted
243 isGoal :: SearchState s => Agendum s -> CaveContext Bool
245 do timeLimit <- asks getTimeLimit
246 return $ Q.length (agendum ^. trail) == (timeLimit - 1)
248 isFullFlow :: SearchState s => s -> CaveContext Bool
250 do cave <- asks getCave
251 cf <- currentFlow state
252 let ff = sumOf (folded . flowRate) cave
256 -- Parse the input file
259 valveP :: Parser (RoomID, Room)
261 tunnelsP :: Parser [RoomID]
262 tunnelTextP :: Parser Text
264 caveP = M.fromList <$> valveP `sepBy` endOfLine
265 valveP = (,) <$> ("Valve " *> (many1 letter)) <*> roomP
266 roomP = roomify <$> (" has flow rate=" *> decimal) <*> (tunnelTextP *> tunnelsP)
267 where roomify v ts = Room {_flowRate = v, _tunnels = ts }
268 tunnelsP = (many1 letter) `sepBy` ", "
269 tunnelTextP = "; tunnels lead to valves " <|> "; tunnel leads to valve "
271 successfulParse :: Text -> Cave
272 successfulParse input =
273 case parseOnly caveP input of
274 Left _err -> M.empty -- TIO.putStr $ T.pack $ parseErrorPretty err