Day 16
This commit is contained in:
@@ -49,15 +49,17 @@ library
|
|||||||
Days.D13
|
Days.D13
|
||||||
Days.D14
|
Days.D14
|
||||||
Days.D15
|
Days.D15
|
||||||
|
Days.D16
|
||||||
build-depends:
|
build-depends:
|
||||||
bytestring
|
bytestring
|
||||||
, either
|
, either
|
||||||
|
, hashable
|
||||||
|
, hashtables ^>=1.3.1
|
||||||
|
, heap ^>=1.0.4
|
||||||
, lens
|
, lens
|
||||||
, megaparsec ^>=9.4.0
|
, megaparsec ^>=9.4.0
|
||||||
, mtl
|
, mtl
|
||||||
, parser-combinators
|
, parser-combinators
|
||||||
, hashable
|
|
||||||
, hashtables ^>=1.3.1
|
|
||||||
, text
|
, text
|
||||||
, vector ^>=0.13.0.0
|
, vector ^>=0.13.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ paths =
|
|||||||
, "./data/13.in"
|
, "./data/13.in"
|
||||||
, "./data/14.in"
|
, "./data/14.in"
|
||||||
, "./data/15.in"
|
, "./data/15.in"
|
||||||
|
, "./data/16.in"
|
||||||
]
|
]
|
||||||
|
|
||||||
solutions :: [(Int, Day, FilePath)]
|
solutions :: [(Int, Day, FilePath)]
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ paths =
|
|||||||
, "./data/13.in"
|
, "./data/13.in"
|
||||||
, "./data/14.in"
|
, "./data/14.in"
|
||||||
, "./data/15.in"
|
, "./data/15.in"
|
||||||
|
, "./data/16.in"
|
||||||
]
|
]
|
||||||
|
|
||||||
solutions :: [(Integer, [Day], FilePath)]
|
solutions :: [(Integer, [Day], FilePath)]
|
||||||
|
|||||||
51
data/16.in
Normal file
51
data/16.in
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
Valve NV has flow rate=5; tunnels lead to valves ZV, CG, YB, HX, OY
|
||||||
|
Valve NU has flow rate=6; tunnels lead to valves DA, MA, OA, DK
|
||||||
|
Valve VU has flow rate=0; tunnels lead to valves PS, FX
|
||||||
|
Valve JW has flow rate=0; tunnels lead to valves AA, MD
|
||||||
|
Valve RI has flow rate=0; tunnels lead to valves OY, DG
|
||||||
|
Valve DG has flow rate=9; tunnels lead to valves TG, RI, DF, EV, KW
|
||||||
|
Valve PH has flow rate=7; tunnels lead to valves KW, OW, LT, LZ
|
||||||
|
Valve KZ has flow rate=12; tunnels lead to valves ET, QV, CK, MS
|
||||||
|
Valve IX has flow rate=0; tunnels lead to valves TS, DO
|
||||||
|
Valve MS has flow rate=0; tunnels lead to valves LZ, KZ
|
||||||
|
Valve IL has flow rate=0; tunnels lead to valves DO, ET
|
||||||
|
Valve EJ has flow rate=20; tunnels lead to valves AV, JY
|
||||||
|
Valve DK has flow rate=0; tunnels lead to valves NU, CG
|
||||||
|
Valve YB has flow rate=0; tunnels lead to valves NV, PS
|
||||||
|
Valve OA has flow rate=0; tunnels lead to valves YA, NU
|
||||||
|
Valve DA has flow rate=0; tunnels lead to valves NU, RG
|
||||||
|
Valve KO has flow rate=0; tunnels lead to valves AA, TG
|
||||||
|
Valve RG has flow rate=4; tunnels lead to valves DF, DA, ZV, MD, LB
|
||||||
|
Valve MA has flow rate=0; tunnels lead to valves AA, NU
|
||||||
|
Valve OW has flow rate=0; tunnels lead to valves DO, PH
|
||||||
|
Valve KW has flow rate=0; tunnels lead to valves DG, PH
|
||||||
|
Valve DO has flow rate=14; tunnels lead to valves IX, IL, CZ, OW
|
||||||
|
Valve DF has flow rate=0; tunnels lead to valves RG, DG
|
||||||
|
Valve TG has flow rate=0; tunnels lead to valves DG, KO
|
||||||
|
Valve LB has flow rate=0; tunnels lead to valves RG, FX
|
||||||
|
Valve HX has flow rate=0; tunnels lead to valves AA, NV
|
||||||
|
Valve GB has flow rate=0; tunnels lead to valves AV, XK
|
||||||
|
Valve CG has flow rate=0; tunnels lead to valves DK, NV
|
||||||
|
Valve LT has flow rate=0; tunnels lead to valves AO, PH
|
||||||
|
Valve FX has flow rate=23; tunnels lead to valves LB, HY, VU
|
||||||
|
Valve ET has flow rate=0; tunnels lead to valves IL, KZ
|
||||||
|
Valve CK has flow rate=0; tunnels lead to valves UX, KZ
|
||||||
|
Valve LZ has flow rate=0; tunnels lead to valves PH, MS
|
||||||
|
Valve YA has flow rate=17; tunnels lead to valves JY, OA
|
||||||
|
Valve TS has flow rate=0; tunnels lead to valves NO, IX
|
||||||
|
Valve NO has flow rate=8; tunnel leads to valve TS
|
||||||
|
Valve XK has flow rate=24; tunnel leads to valve GB
|
||||||
|
Valve PS has flow rate=18; tunnels lead to valves EV, VU, YB
|
||||||
|
Valve AA has flow rate=0; tunnels lead to valves JW, HX, MA, KO
|
||||||
|
Valve MD has flow rate=0; tunnels lead to valves JW, RG
|
||||||
|
Valve JM has flow rate=19; tunnels lead to valves QV, HY, AO
|
||||||
|
Valve AV has flow rate=0; tunnels lead to valves EJ, GB
|
||||||
|
Valve AO has flow rate=0; tunnels lead to valves JM, LT
|
||||||
|
Valve JY has flow rate=0; tunnels lead to valves YA, EJ
|
||||||
|
Valve OY has flow rate=0; tunnels lead to valves NV, RI
|
||||||
|
Valve UX has flow rate=13; tunnels lead to valves CZ, CK
|
||||||
|
Valve HY has flow rate=0; tunnels lead to valves JM, FX
|
||||||
|
Valve EV has flow rate=0; tunnels lead to valves PS, DG
|
||||||
|
Valve CZ has flow rate=0; tunnels lead to valves UX, DO
|
||||||
|
Valve ZV has flow rate=0; tunnels lead to valves NV, RG
|
||||||
|
Valve QV has flow rate=0; tunnels lead to valves JM, KZ
|
||||||
214
src/Days/D16.hs
Normal file
214
src/Days/D16.hs
Normal 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
|
||||||
|
)
|
||||||
@@ -27,6 +27,7 @@ import Days.D12 qualified as D12
|
|||||||
import Days.D13 qualified as D13
|
import Days.D13 qualified as D13
|
||||||
import Days.D14 qualified as D14
|
import Days.D14 qualified as D14
|
||||||
import Days.D15 qualified as D15
|
import Days.D15 qualified as D15
|
||||||
|
import Days.D16 qualified as D16
|
||||||
|
|
||||||
import Data.ByteString.Char8 qualified as BS
|
import Data.ByteString.Char8 qualified as BS
|
||||||
import Data.Text.IO qualified as T
|
import Data.Text.IO qualified as T
|
||||||
@@ -54,4 +55,5 @@ days =
|
|||||||
, [D13.day]
|
, [D13.day]
|
||||||
, [D14.day]
|
, [D14.day]
|
||||||
, [D15.day]
|
, [D15.day]
|
||||||
|
, [D16.day]
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user