aboutsummaryrefslogtreecommitdiff
path: root/2021/day19/19.hs
diff options
context:
space:
mode:
authorAria <me@aria.rip>2023-01-02 21:58:56 +0000
committerAria <me@aria.rip>2023-01-02 21:58:56 +0000
commit5eb58ad076f2cd435b11b140820da224b60b73d5 (patch)
tree2a67939595fbf993ff04f69b9cd3f0aa20827d96 /2021/day19/19.hs
initial commit
Diffstat (limited to '2021/day19/19.hs')
-rw-r--r--2021/day19/19.hs108
1 files changed, 108 insertions, 0 deletions
diff --git a/2021/day19/19.hs b/2021/day19/19.hs
new file mode 100644
index 0000000..a2cabb3
--- /dev/null
+++ b/2021/day19/19.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Data.Monoid (Endo (Endo), appEndo)
+import Data.Maybe (listToMaybe, isJust)
+import Data.List (isPrefixOf)
+import Linear.Vector ((^+^), (^-^))
+import Linear.V3 (V3 (V3))
+
+import Data.Set (Set)
+
+import qualified Data.Set as S
+import qualified Data.Map as M
+import qualified Data.Text as T
+
+type Vec3 = V3 Integer
+type Transform = Endo Vec3
+data Scanner = Scanner { beacons :: [Vec3]
+ } deriving (Show, Eq)
+data PositionedScanner = PositionedScanner { scanner :: Scanner
+ ,originOffset :: Vec3
+ } deriving (Show)
+
+instance Show Transform where
+ show c = show $ appEndo c (V3 0 0 0)
+
+nullTrans = Endo id
+rotX = Endo \(V3 x y z) -> V3 x (- z) y
+rotY = Endo \(V3 x y z) -> V3 z y (- x)
+rotZ = Endo \(V3 x y z) -> V3 (- y) x z
+translate v = Endo (v ^+^)
+
+rotations :: [Transform]
+rotations = [a <> b | a <- ras, b <- rbs]
+ where ras = [ nullTrans, rotY, rotY <> rotY, rotY <> rotY <> rotY
+ , rotZ, rotZ <> rotZ <> rotZ]
+ rbs = [nullTrans, rotX, rotX <> rotX, rotX <> rotX <> rotX]
+
+threshold :: Integer
+threshold = 12
+
+firstJust :: [Maybe a] -> Maybe a
+firstJust xs | null js = Nothing
+ | otherwise = (head js)
+ where js = filter isJust xs
+
+parseFile :: String -> [[Vec3]]
+parseFile s = reverse $ parseLines (tail $ lines s) [[]]
+ where parseLines [] cs = cs
+ parseLines (l:ls) (c:cs) | "---" `isPrefixOf` l = parseLines ls ([] : c : cs)
+ | null l = parseLines ls (c : cs)
+ | otherwise = parseLines ls ((parseLine l : c) : cs)
+ parseLine l = V3 x y z
+ where [x, y, z] = map (read . T.unpack) $ T.splitOn "," $ T.pack l
+
+commonOffset :: [Vec3] -> [Vec3] -> Maybe Vec3
+commonOffset ys xs = listToMaybe aboveThreshold >>= (Just . fst)
+ where dists = [x ^-^ y | x <- xs, y <- ys]
+ distCounts = M.toList $ M.fromListWith (+) [(d, 1) | d <- dists]
+ aboveThreshold = filter ((>= threshold) . snd) distCounts
+
+applyTransform :: Transform -> Scanner -> Scanner
+applyTransform t (Scanner bs) = Scanner (map (appEndo t) bs)
+
+-- attempt to get a's offset from b
+offsetFrom :: Scanner -> Scanner -> Maybe (Vec3, Scanner)
+offsetFrom a b = listToMaybe successes
+ where attempts = [attemptWith rot | rot <- rotations]
+ successes = [(a, b) | (Just a, b) <- attempts]
+ attemptWith rot = (commonOffset (beacons a') (beacons b), a')
+ where a' = applyTransform rot a
+
+adjustedOffsetFrom :: PositionedScanner -> Scanner -> Maybe PositionedScanner
+adjustedOffsetFrom b a = case a `offsetFrom` (scanner b) of
+ Just (off, sc) -> Just $ PositionedScanner sc (off ^+^ (originOffset b))
+ Nothing -> Nothing
+
+solveMore :: [PositionedScanner] -> [Scanner] -> ([PositionedScanner], [Scanner])
+solveMore ks us = foldr solveOne (ks, us) us
+ where solveOne s (ks', us') = case firstJust (map (\k -> adjustedOffsetFrom k s) ks') of
+ Just d -> (d : ks', filter (/= s) us')
+ Nothing -> (ks', us')
+
+calcAllOffsets :: [Scanner] -> [PositionedScanner]
+calcAllOffsets (s:ss) = keepSolvingMore ([PositionedScanner s (V3 0 0 0)], ss)
+ where keepSolvingMore (ks,[]) = ks
+ keepSolvingMore (ks,us) = keepSolvingMore (solveMore ks us)
+
+absoluteBeacons :: PositionedScanner -> Set Vec3
+absoluteBeacons (PositionedScanner sc pos) = S.fromList $ map (pos ^+^) (beacons sc)
+
+manhattan :: Vec3 -> Vec3 -> Integer
+manhattan (V3 x y z) (V3 x' y' z') = (abs (x' -x)) + (abs (y' - y)) + (abs (z' - z))
+
+main :: IO ()
+main = do
+ input <- readFile "./input"
+ let parsed = map Scanner $ parseFile input
+ let positioned = calcAllOffsets parsed
+ let beacons = foldr S.union S.empty $ map absoluteBeacons positioned
+
+ print $ "Part 1: " ++ (show $ S.size beacons)
+
+ let scannerPositions = map originOffset positioned
+ print $ "Part 2: " ++ (show $ maximum [manhattan a b | a <- scannerPositions, b <- scannerPositions])