-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay16.hs
165 lines (137 loc) · 5.17 KB
/
Day16.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
159
160
161
162
163
164
165
{-# LANGUAGE TupleSections #-}
module Day16 (solve) where
import Data.Function ( (&) )
import Data.List
import qualified Data.List.Split as Split
import qualified Data.Map as Map
data Valve = Valve
{ name :: String
, rate :: Int
, leads :: [String]
} deriving (Show)
data Helper = Helper
{ route :: [String]
, timeLeft :: Int
, extraRate :: Int
} deriving (Show, Eq)
data State = State
{ helpers :: [Helper]
, minsLeft :: Int
, targetsLeft :: [String]
, aggRate :: Int
, reduction :: Int
} deriving (Show, Eq)
solve input lines = do
let valveList = readValves lines
let getValve = getValveFromList valveList
let getDist = getDistanceFromValves valveList getValve
let allTargets = valveList & filter ((> 0) . rate) & map name
let add = addRoute getValve getDist
let expand = includeAndExpand add
let expandAndPrune = prune 5000 . expand
let start = getStart allTargets 26
print $
iterate expandAndPrune [start] !! length allTargets
& head & waitOut
prune size states =
states
& filter ((> 0) . timeLeft . fstHlp)
& nub
& sortOnDesc waitOut
& take size
includeAndExpand add states = states ++ expandStates add states
expandStates add states = do concatMap (expandState add) states
expandState add state = map (add state) (targetsLeft state)
addRoute getValve getDist state next = do
let nextValve = getValve next
let (first:rest) = helpers state
let route' = next : route first
let travelTime = getDist (head (route first)) next + 1
let timeLeft' = timeLeft first - travelTime
let minsLeft' = timeLeft first
let extraRate' = rate nextValve
let targetsLeft' = delete next (targetsLeft state)
let reduction' =
reduction state + ((minsLeft state - minsLeft') * aggRate state)
let aggRate' = aggRate state + extraRate first
let first' = Helper
{route = route'
, timeLeft = timeLeft'
, extraRate = extraRate'
}
State { helpers = sortOnDesc timeLeft (first' : rest)
, minsLeft = minsLeft'
, targetsLeft = targetsLeft'
, aggRate = aggRate'
, reduction = reduction' }
fstHlp state = helpers state & head
waitOut state = do
let (first:second:_) = helpers state
let preSpan = minsLeft state - maxZero (timeLeft first)
let reduction' = reduction state + preSpan * aggRate state
let aggRate' = aggRate state + extraRate first
let firstSpan = maxZero (timeLeft first) - maxZero (timeLeft second)
let reduction'' = reduction' + firstSpan * aggRate'
let aggRate'' = aggRate' + extraRate second
let secondSpan = maxZero (timeLeft second)
let reduction''' = reduction'' + secondSpan * aggRate''
reduction'''
getStart targets time =
State { helpers =
[ Helper {route = ["AA"], timeLeft = time, extraRate = 0 }
, Helper {route = ["AA"], timeLeft = time, extraRate = 0 } ]
, minsLeft = time
, targetsLeft = targets
, aggRate = 0
, reduction = 0 }
getDistanceFromValves valves getValve = do
\ n1 n2 -> do
let (Just distance) = Map.lookup (n1, n2) distMap
distance
where
addValve distMap v = do
let pairs = map (:[name v]) (leads v)
foldl (\ dm [n1, n2] -> Map.insert (n1, n2) 1 dm) distMap pairs
neighbourDist = foldl addValve Map.empty valves
addIfShorter dist dm valvePair = do
let existing = Map.lookup valvePair dm
case existing of
Nothing -> Map.insert valvePair dist dm
(Just existingDist) ->
if dist < existingDist then
Map.insert valvePair dist dm
else
dm
expand dm oDist =
dm & Map.toList & filter ((== oDist) . snd)
& concatMap (\ ((n1, n2), d) -> do
leads (getValve n2) & filter (/= n1) & map (n1,) )
& nub
& sort
& foldl (addIfShorter (oDist + 1)) dm
distMap = (neighbourDist, 1) &
unfoldr (\ (dm, d) -> do
let expanded = expand dm d
let r = (expanded, d + 1)
Just (r, r) )
& pairwise
& dropWhile (\ (a, b) -> fst a /= fst b)
& head & fst & fst
getValveFromList valveList = do
let vMap = valveList & map (\ v -> (name v, v)) & Map.fromList
\ name -> Map.lookup name vMap & \ (Just vlv) -> vlv
readValves lines = do
map readValve lines
where
readValve line = do
let parts = Split.splitOn " " line
let id = parts !! 1
let rate =
parts !! 4 & Split.splitOn "=" & (!! 1) & init & read :: Int
let reachable = drop 9 parts & map (dropWhileEnd (== ','))
Valve {name = id, rate = rate, leads = reachable}
pairwise l = zip l (tail l)
sortDesc :: Ord a => [a] -> [a]
sortDesc = reverse . sort
sortOnDesc pred = reverse . sortOn pred
maxZero = max 0