-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.hs
140 lines (120 loc) · 4.3 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
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}
import AoC
import AoC.Grid
import Data.Bifunctor
import Data.Char
import Data.List.Split
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
type N = Int
type Beacon = V3 N
data Scanner = Scanner { scannerId :: Int
, scannerBeacons :: Set Beacon
}
deriving Show
-- TODO: Cache "diffs", might be easier to match scanners if some
-- vectors are calculated.
shift :: V3 N -> Set Beacon -> Set Beacon
shift d = Set.map (+ d)
rotations =
[ \case V3 (x, y, z) -> V3 ( x, y, z)
, \case V3 (x, y, z) -> V3 ( x, z, -y)
, \case V3 (x, y, z) -> V3 ( x, -y, -z)
, \case V3 (x, y, z) -> V3 ( x, -z, y)
, \case V3 (x, y, z) -> V3 ( y, x, -z)
, \case V3 (x, y, z) -> V3 ( y, z, x)
, \case V3 (x, y, z) -> V3 ( y, -x, z)
, \case V3 (x, y, z) -> V3 ( y, -z, -x)
, \case V3 (x, y, z) -> V3 ( z, x, y)
, \case V3 (x, y, z) -> V3 ( z, y, -x)
, \case V3 (x, y, z) -> V3 ( z, -x, -y)
, \case V3 (x, y, z) -> V3 ( z, -y, x)
, \case V3 (x, y, z) -> V3 (-x, y, -z)
, \case V3 (x, y, z) -> V3 (-x, z, y)
, \case V3 (x, y, z) -> V3 (-x, -y, z)
, \case V3 (x, y, z) -> V3 (-x, -z, -y)
, \case V3 (x, y, z) -> V3 (-y, x, z)
, \case V3 (x, y, z) -> V3 (-y, z, -x)
, \case V3 (x, y, z) -> V3 (-y, -x, -z)
, \case V3 (x, y, z) -> V3 (-y, -z, x)
, \case V3 (x, y, z) -> V3 (-z, x, -y)
, \case V3 (x, y, z) -> V3 (-z, y, x)
, \case V3 (x, y, z) -> V3 (-z, -x, y)
, \case V3 (x, y, z) -> V3 (-z, -y, -x)
]
scannerRotations :: Set Beacon -> [Set Beacon]
scannerRotations s = map (\r -> Set.map r s) rotations
-- returns scanner pos relative to first scanner + rotated and shifted scanner
match :: Scanner -> Scanner -> Maybe (V3 N, Scanner)
match (Scanner _ s1) (Scanner s2id s2) = listToMaybe do
-- Pick rotation
-- Pick anchor beacon and scanner beacon
-- Shift
-- Check intersection
-- If intersection >= 12 => locked
rotated <- scannerRotations s2
anchor <- take 15 (Set.toList s1) -- 15 makes sure we get at least
-- 1 from the overlap (26 - 12 =
-- 14)
anchor2 <- Set.toList rotated
let d = anchor - anchor2
shifted = shift d rotated
overlap = s1 `Set.intersection` shifted
if length overlap >= 12
then pure (d, (Scanner s2id shifted))
else []
-- TODO: Calculate the cache on-the-fly instead
cache :: [Scanner] -> Set (Int, Int)
cache scanners =
let pairs = [ (s1, s2) | s1 <- scanners
, s2 <- scanners
, scannerId s1 < scannerId s2
]
in Set.fromList
. map (bimap scannerId scannerId)
. filter (isJust . uncurry match)
$ pairs
cachedMatch :: Set (Int, Int) -> Scanner -> Scanner -> Maybe (V3 N, Scanner)
cachedMatch c base@(Scanner bid _) candidate@(Scanner cid _)
| (min bid cid, max bid cid) `Set.member` c =
match base candidate
| otherwise = Nothing
merge :: [Scanner] -> [(V3 N, Scanner)]
merge (s:scanners) = go [(0, s)] scanners
where c = cache (s:scanners)
go locked = \case
[] -> locked
available ->
let (merged, rem) = partitionWith (matchWith locked) available
in
go (locked ++ merged) rem
matchWith locked candidate =
let matched = mapMaybe (\(_, u) -> cachedMatch c u candidate) locked
in case matched of
x:_ -> Left x
_ -> Right candidate
parse :: [String] -> Scanner
parse (header:beacons) =
Scanner { scannerId = read . takeWhile isDigit . drop 12 $ header
, scannerBeacons = Set.fromList (map f beacons)
}
where f x = v3 . read $ "(" ++ x ++ ")"
parseAll = map parse . splitOn [""] . lines
part1 :: [(V3 N, Scanner)] -> Int
part1 = length . Set.unions . map (scannerBeacons . snd)
part2 solved =
let positions = map fst solved
in maximum $ [ sum (abs (p1 - p2)) | p1 <- positions
, p2 <- positions
]
main = main' "input.txt"
exampleMain = main' "example.txt"
main' file = do
input <- parseAll <$> readFile file
let solved = merge input
print (part1 solved)
print (part2 solved)