1 {-# LANGUAGE NegativeLiterals #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE OverloadedStrings #-}
4 {-# LANGUAGE TypeFamilies #-}
5 {-# LANGUAGE BangPatterns #-}
7 -- Box division approach taken from fizbin:
8 -- https://www.reddit.com/r/adventofcode/comments/a8s17l/2018_day_23_solutions/ecfmpy0/
12 -- import Prelude hiding ((++))
13 import Data.Text (Text)
14 import qualified Data.Text as T
15 import qualified Data.Text.IO as TIO
17 import Data.Void (Void)
18 import Text.Megaparsec hiding (State)
19 import Text.Megaparsec.Char
20 import qualified Text.Megaparsec.Char.Lexer as L
21 import qualified Control.Applicative as CA
23 import qualified Data.Map.Strict as M
24 import Data.Map.Strict ((!))
28 import Linear (V3(..), (^-^))
30 import qualified Data.PQueue.Max as P
33 type Coord = V3 Integer -- x, y, z
34 type BotSwarm = M.Map Coord Integer
35 -- type VertexCounts = M.Map Coord Integer
36 type Box = (Coord, Coord)
37 data LabelledBox = LabelledBox { _box :: Box
38 , _intersectCount :: Int
40 -- instance Ord LabelledBox where
41 -- lb1 `compare` lb2 = if (_intersectCount lb1) /= (_intersectCount lb2)
42 -- then (_intersectCount lb1) `compare` (_intersectCount lb2)
43 -- else if (boxSize lb1) /= (boxSize lb2)
44 -- then (boxSize lb1) `compare` (boxSize lb2)
45 -- else (distanceFromOrigin lb1) `compare` (distanceFromOrigin lb2)
46 -- where boxSize lb = manhattan (fst $ _box lb) (snd $ _box lb)
47 -- distanceFromOrigin lb = min (distance $ fst $ _box lb) (distance $ snd $ _box lb)
48 instance Ord LabelledBox where
49 lb1 `compare` lb2 = if (_intersectCount lb1) /= (_intersectCount lb2)
50 then (_intersectCount lb1) `compare` (_intersectCount lb2)
51 else if (boxSize lb1) /= (boxSize lb2)
52 then (boxSize lb1) `compare` (boxSize lb2)
53 else (distanceFromOrigin $ _box lb1) `compare` (distanceFromOrigin $ _box lb2)
55 boxSize lb = manhattan (fst $ _box lb) (snd $ _box lb)
56 distanceFromOrigin lb = boxDistanceFromPoint lb origin
58 type BoxQueue = P.MaxQueue LabelledBox
67 text <- TIO.readFile "data/advent23.txt"
68 let bots = successfulParse text
69 let swarm = enSwarm bots
73 -- print $ zip [0..] instrs
74 -- print $ part1 ip instrs
75 -- print $ part2 ip instrs
78 part1 :: BotSwarm -> Int
79 part1 swarm = M.size inRangeBots
80 where centre = strongest swarm
82 botInRange loc _ = (manhattan loc centre) <= range
83 inRangeBots = M.filterWithKey botInRange swarm
86 -- part2 swarm = ((distance $ snd best), best)
87 -- where vcs = vertexCounts vs swarm
88 -- vs = verticesOfSwarm swarm
89 -- best = targetVertex vcs
94 manhattan :: Coord -> Coord -> Integer
95 -- manhattan $ (V3 0 0 0) ^-^ (V3 1 3 1)
96 manhattan (V3 x y z) (V3 x' y' z') = (abs (x - x')) + (abs (y - y')) + (abs (z - z'))
98 distance :: Coord -> Integer
99 distance = manhattan origin
101 enSwarm :: [(Coord, Integer)] -> BotSwarm
102 enSwarm = foldl' (\s (c, r) -> M.insert c r s) M.empty
105 strongest :: BotSwarm -> Coord
106 strongest swarm = fst $ M.foldlWithKey' findStrongest pair0 swarm
107 where findStrongest (currentCoord, currentMax) coord range =
108 if range > currentMax
110 else (currentCoord, currentMax)
111 pair0 = M.findMin swarm
114 boxIntersectionCount :: Box -> BotSwarm -> Int
115 boxIntersectionCount box swarm = M.size $ M.filterWithKey (\b _ -> intersects box b swarm) swarm
117 intersects :: Box -> Coord -> BotSwarm -> Bool
118 intersects box bot swarm =
120 where d = boxDistanceFromPoint box bot
124 boxDistanceFromPoint :: Box -> Coord -> Integer
125 boxDistanceFromPoint ((V3 l f t), (V3 r b u)) (V3 x y z) = d
127 -- # returns whether box intersects bot
129 -- for i in (0, 1, 2):
130 -- boxlow, boxhigh = box[0][i], box[1][i] - 1
131 -- d += abs(bot[i] - boxlow) + abs(bot[i] - boxhigh)
132 -- d -= boxhigh - boxlow
134 -- return d <= bot[3]
137 -- for( i = 0; i < 3; i++ ) {
138 -- if( C[i] < Bmin[i] ) dmin += SQR( C[i] - Bmin[i] ); else
139 -- if( C[i] > Bmax[i] ) dmin += SQR( C[i] - Bmax[i] );
141 -- if( dmin <= r2 ) return TRUE;
144 where d = sum [ dist boxLow boxHigh coord
145 | (boxLow, boxHigh, coord)
146 <- [(l, r, x), (f, b, y), (t, u, z)]
148 dist bl bh v = (if v <= bl then abs (v - bl) else 0)
150 (if v >= bh then abs (v - bh) else 0)
153 subBoxes :: Box -> [Box]
154 subBoxes ((V3 l f t), (V3 r b u)) =
155 [ ((V3 l f t ) , (V3 r' b' u'))
156 , ((V3 l f t'), (V3 r' b' u ))
157 , ((V3 l f' t ), (V3 r' b u'))
158 , ((V3 l f' t'), (V3 r' b u ))
159 , ((V3 l' f t ) , (V3 r b' u'))
160 , ((V3 l' f t'), (V3 r b' u ))
161 , ((V3 l' f' t ), (V3 r b u'))
162 , ((V3 l' f' t'), (V3 r b u ))
164 where w = (r - l) `div` 2
173 unitBox :: Box -> Bool
174 unitBox ((V3 l f t), (V3 r b u)) = l == r && f == b && t == u
177 boundingBox swarm = ((V3 minX minY minZ), (V3 maxX maxY maxZ))
178 where minX = minimum $ [ _x bot | bot <- M.keys swarm ]
179 minY = minimum $ [ _y bot | bot <- M.keys swarm ]
180 minZ = minimum $ [ _z bot | bot <- M.keys swarm ]
181 maxX = maximum $ [ _x bot | bot <- M.keys swarm ]
182 maxY = maximum $ [ _y bot | bot <- M.keys swarm ]
183 maxZ = maximum $ [ _z bot | bot <- M.keys swarm ]
189 part2 = distanceFromOrigin . bestUnitBox
191 bestUnitBox :: BotSwarm -> Box
192 bestUnitBox swarm = findBestBox swarm initialQueue
193 where initialBox = boundingBox swarm
194 initialQueue = P.singleton $ enLabel swarm initialBox
196 findBestBox :: BotSwarm -> BoxQueue -> Box
197 findBestBox swarm queue
198 | unitBox currentBox = currentBox
199 | otherwise = findBestBox swarm newQueue
200 where (currentLBox, queue') = P.deleteFindMax queue
201 currentBox = _box currentLBox
202 nextBoxes = subBoxes currentBox
203 nextLBoxes = map (enLabel swarm) nextBoxes
204 newQueue = foldl' (\ q b -> P.insert b q) queue' nextLBoxes
206 enLabel :: BotSwarm -> Box -> LabelledBox
207 enLabel swarm box = LabelledBox { _box = box, _intersectCount = boxIntersectionCount box swarm}
210 type Parser = Parsec Void Text
213 sc = L.space (skipSome spaceChar) CA.empty CA.empty
216 integer = lexeme L.decimal
217 signedInteger = L.signed sc integer
220 posOpenP = symb "pos=<"
222 radiusStartP = symb "r="
224 swarmP = many nanobotP
225 nanobotP = (,) <$> posP <* comma <*> radiusP
227 posP = posify <$> (posOpenP `between` posCloseP) coordP
228 where posify (a, b, c) = V3 a b c
229 coordP = (,,) <$> signedInteger <* comma <*> signedInteger <* comma <*> signedInteger
230 radiusP = radiusStartP *> signedInteger
233 successfulParse :: Text -> [(Coord, Integer)]
234 -- successfulParse _ = []
235 successfulParse input =
236 case parse swarmP "input" input of
237 Left _error -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err