-
Notifications
You must be signed in to change notification settings - Fork 0
/
run.hs
158 lines (142 loc) · 4.97 KB
/
run.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
154
155
156
157
158
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
import AoC
import AoC.Parse (numP)
import AoC.Search (bfs_)
import Control.Monad (guard, forM_)
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import Data.Ix (index, rangeSize)
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Maybe (fromJust)
import Data.Vector (Vector)
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV
import Data.Void (Void)
import Text.Megaparsec
import Text.Megaparsec.Char
type Valve = (Char, Char)
type N = Int
type Parser a = Parsec Void String a
type IxValve = Int
ix :: Valve -> IxValve
ix = index bounds
bounds :: ((Char, Char), (Char, Char))
bounds = (('A', 'A'), ('Z', 'Z'))
nameP :: Parser Valve
nameP = (,) <$> upperChar <*> upperChar
valveP :: Parser (Valve, (Int, [Valve]))
valveP = do
valve <- string "Valve " *> nameP
rate <- string " has flow rate=" *> numP <* string ";"
_ <- choice [ string " tunnels lead to valves "
, string " tunnel leads to valve "
]
valves <- sepBy1 nameP (string ", ")
pure (valve, (rate, valves))
parseAll :: String -> IntMap (N, [IxValve])
parseAll =
IntMap.fromList
. map (\(Right (v, (rate, valves))) -> (ix v, (rate, map ix valves)))
. map (parse valveP "")
. lines
fromIntMap :: IntMap a -> Vector a
fromIntMap m = V.create do
vec <- MV.new (rangeSize bounds)
forM_ (IntMap.toList m) \(i, v) ->
MV.write vec i v
pure vec
compact :: IntMap (N, [IxValve]) -> Vector (N, [(Int, IxValve)])
compact graph =
let nonZero = IntMap.filter ((> 0) . fst) graph
neighbors node = snd $ graph IntMap.! node
costs from =
[ (fromJust $ bfs_ (== to) neighbors from, to)
| to <- IntMap.keys nonZero
, from /= to
]
in
fromIntMap . IntMap.fromList
$ [ (from, (flow, costs from))
| (from, (flow, _)) <- (ix ('A', 'A'), graph IntMap.! ix ('A', 'A')):IntMap.toList nonZero
]
part1 :: IntMap (N, [IxValve]) -> N
part1 vs =
let compacted = compact vs
flows = IntMap.filter (> 0) $ IntMap.map fst vs
potential (_, t, _, p, _) = max 0 (t - 1) * p
neighbors (!current, !t, !open, !p, !released) = do
let (_, nexts) = compacted V.! current
(steps, next) <- nexts
guard $ steps < t
guard $ not $ next `IntSet.member` open
let open' = IntSet.insert next open
(flow, _) = compacted V.! next
pure ( next
, t-steps-1
, open'
, p - flow
, released + (t-steps-1)*flow
)
go m =
\case [] -> m
c@(_, t, _, p, released):rest
| t <= 0 || p <= 0 -> go (max released m) rest
| released + potential c < m -> go m rest
| otherwise ->
let nbhd = neighbors c
in
if null nbhd
then go (max m released) rest
else go m (nbhd ++ rest)
in
go 0 [(ix ('A', 'A'), 30, IntSet.singleton (ix ('A', 'A')), sum flows, 0)]
part2 :: IntMap (N, [IxValve]) -> N
part2 vs = go 0 [( 26
, ix ('A', 'A')
, ix ('A', 'A')
, 0
, IntSet.singleton (ix ('A', 'A'))
, sum flows
, 0
)]
where compacted = compact vs
flows = IntMap.filter (> 0) $ IntMap.map fst vs
potential (t, _, _, _, _, p, _) = max 0 (t - 1) * p
go m = \case
[] -> m
c@(t, node, ttarget, trem, open, p, released):rest
| t <= 0 || p == 0 -> go (max released m) rest
| released + potential c < m -> go m rest
| otherwise ->
let (_, nexts) = compacted V.! node
nbhd = do
(steps, next) <- nexts
guard $ not $ next `IntSet.member` open
guard $ steps < t
let (flow, _) = compacted V.! next
steps' = steps + 1
pressure = max 0 $ flow * (t - steps')
p' = p - flow
open' = IntSet.insert next open
pure $ case compare steps' trem of
LT -> (t - steps', next, ttarget, trem - steps', open', p', released + pressure)
GT -> (t - trem , ttarget, next, steps' - trem, open', p', released + pressure)
EQ -> (t - trem , ttarget, next, steps' - trem, open', p', released + pressure)
in
if null nbhd
then go (max released m) rest
else go m (nbhd ++ rest)
main :: IO ()
main = main' "input.txt"
exampleMain :: IO ()
exampleMain = main' "example.txt"
main' :: FilePath -> IO ()
main' file = do
input <- parseAll <$> readFile file
print (part1 input)
print (part2 input)