aboutsummaryrefslogtreecommitdiff
path: root/2021/day19/19.hs
blob: a2cabb3e197aee40581a9124d8356c9338a6d82b (plain)
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
{-# 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])