This commit is contained in:
Christian
2023-10-18 10:47:20 +02:00
parent 402aa9fb80
commit dcf6f48993
6 changed files with 273 additions and 2 deletions

214
src/Days/D16.hs Normal file
View File

@@ -0,0 +1,214 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Days.D16 where
import Common
import Data.Char (isAsciiUpper)
import Data.List (foldl', sortOn)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (fromJust, fromMaybe, isJust)
import Data.Ord (Down (..))
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Tuple (swap)
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Parse
import Text.Megaparsec (sepBy, takeWhile1P, (<|>))
import Text.Megaparsec.Char (space)
import Text.Megaparsec.Char.Lexer qualified as Lexer
type ValveName = Text
parser :: Parser (Map ValveName Int, Map ValveName [ValveName])
parser = makeMaps <$> someLines nodeDescription
where
symbol = Lexer.symbol space
lexeme = Lexer.lexeme space
valve :: Parser Text
valve = takeWhile1P Nothing isAsciiUpper
nodeDescription =
(,,)
<$ symbol "Valve"
<*> lexeme valve
<* symbol "has flow rate="
<*> number
<* symbol ";"
<* ( symbol "tunnels lead to valves"
<|> symbol "tunnel leads to valve"
)
<*> sepBy valve (symbol ",")
makeMaps :: [(Text, Int, [Text])] -> (Map Text Int, Map Text [Text])
makeMaps intermediate =
let (valve, rate, connections) = unzip3 intermediate
in ( Map.fromList (zip valve rate),
Map.fromList (zip valve connections)
)
floydWarshall :: (Ord a) => Map a [a] -> Map (a, a) Int
floydWarshall m =
let keys = Map.keys m
in foldl'
( \acc (k, i, j) ->
Map.alter
( let newPath =
do
dist_i_k <- Map.lookup (i, k) acc
dist_k_j <- Map.lookup (k, j) acc
return $ dist_i_k + dist_k_j
in \case
Nothing -> newPath
Just v -> Just $ case newPath of
Nothing -> v
Just newPath_ -> min v newPath_
)
(i, j)
acc
)
(Map.fromList [((a, b), 1) | a <- keys, b <- fromMaybe [] $ Map.lookup a m])
[(k, i, j) | k <- keys, i <- keys, j <- keys]
data Network = Network
{ rates :: Map ValveName Int,
nextBest :: Map ValveName (Vector (ValveName, Int))
}
preProcess ::
Map ValveName Int ->
Map ValveName [ValveName] ->
Network
preProcess rates network =
let distances = floydWarshall network
possibleStops = Map.filterWithKey (\k v -> v > 0 || k == "AA") rates
possibleStopsV = Vector.fromList . Map.toList $ possibleStops
nextBest =
Map.fromList
[ ( from,
Vector.fromList
. map snd
. sortOn (Down . fst)
$ [ (rate, (to, dist + 1))
| (to, rate) <- Vector.toList possibleStopsV,
rate > 0,
let maybeDist = Map.lookup (from, to) distances,
isJust maybeDist,
let dist = fromJust maybeDist
]
)
| (from, _) <- Vector.toList possibleStopsV
]
in Network
{ rates = possibleStops,
nextBest = nextBest
}
data SearchState = SearchState
{ pressure :: Int,
opened :: Set ValveName,
pos :: (ValveName, ValveName),
time :: (Int, Int)
}
maximizePressure ::
Int ->
Bool ->
ValveName ->
Network ->
Int
maximizePressure deadline withElephant start (Network {..}) =
go
0
SearchState
{ pressure = 0,
opened = Set.empty,
pos = (start, start),
time = (deadline, if withElephant then deadline else 0)
}
where
go :: Int -> SearchState -> Int
go best searcher =
let nextStates = branch searcher
in if null nextStates
then pressure searcher
else
foldl'
( \best_ nextSearcher ->
if bound nextSearcher > best_
then max (go best_ nextSearcher) best_
else best_
)
best
nextStates
branch :: SearchState -> [SearchState]
branch SearchState {..} =
[ let f = if nextTime < snd time then swap else id
in SearchState
{ pressure = pressure + rate * nextTime,
opened = Set.insert nextPos opened,
pos = f (nextPos, snd pos),
time = f (nextTime, snd time)
}
| (nextPos, dist) <-
maybe [] Vector.toList (Map.lookup (fst pos) nextBest),
fst time > dist,
not $ Set.member nextPos opened,
let nextTime = fst time - dist,
let rate = fromJust (Map.lookup nextPos rates)
]
heuristic :: Vector (Vector (ValveName, Int, Int))
heuristic =
Vector.fromList
[ Vector.reverse
. Vector.fromList
. sortOn (\(_, min_dist, rate) -> (time - min_dist) * rate)
$ [ (valve, min_dist, rate)
| (valve, rate) <- Map.toList rates,
let min_dist =
minimum
. Vector.map snd
. fromJust
$ Map.lookup valve nextBest,
time > min_dist
]
| time <- [0 .. deadline]
]
bound :: SearchState -> Int
bound SearchState {..} = uncurry (go opened) time pressure
where
go opened max_t min_t bound =
case Vector.toList
. Vector.filter (\(valve, _, _) -> not $ Set.member valve opened)
$ (heuristic Vector.! max_t) of
[] -> bound
((valveId, min_dist, flow) : _) ->
let max_t' = max_t - min_dist
bound' = bound + flow * max_t'
min_t' = min min_t max_t'
max_t'' = max min_t max_t'
opened' = Set.insert valveId opened
in go opened' max_t'' min_t' bound'
part1 :: Network -> Int
part1 = maximizePressure 30 False "AA"
part2 :: Network -> Int
part2 = maximizePressure 26 True "AA"
day :: Day
day =
parsecDay
(uncurry preProcess <$> parser)
( definitive . part1,
definitive . part2
)

View File

@@ -27,6 +27,7 @@ import Days.D12 qualified as D12
import Days.D13 qualified as D13
import Days.D14 qualified as D14
import Days.D15 qualified as D15
import Days.D16 qualified as D16
import Data.ByteString.Char8 qualified as BS
import Data.Text.IO qualified as T
@@ -54,4 +55,5 @@ days =
, [D13.day]
, [D14.day]
, [D15.day]
, [D16.day]
]