-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay10.hs
135 lines (112 loc) · 4.11 KB
/
Day10.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
module Day10
( part1
, part2
) where
import Data.List (group, groupBy, maximumBy, nub, sort, sortBy,
tails)
import Data.List as L (filter, map)
import Data.MultiSet as MS (MultiSet, foldOccur, fromList, map)
import Data.Set as St (Set, delete, difference, empty, filter,
foldl', fromList, insert, map, size,
toList)
import Linear.V2 (V2 (..))
type Pos = V2 Int
type AstMap = [Pos]
type Line = (Maybe Slope, Offset)
type Slope = Rational
type Offset = Rational
numBet = 200
aligned :: Line -> (Pos -> Bool) -> Pos -> Bool
aligned (Nothing, offset) half p@(V2 x _) = half p && fromIntegral x == offset
aligned (Just slope, offset) half p@(V2 x y) =
half p && fromIntegral y == fromIntegral x * slope + offset
line :: Pos -> Pos -> Line
line from@(V2 xf yf) to@(V2 xt yt)
| xf == xt = (Nothing, fromIntegral xf)
| otherwise = (Just slope, offset)
where
slope = fromIntegral (yf - yt) / fromIntegral (xf - xt) :: Rational
offset = fromIntegral yf - slope * fromIntegral xf
between :: Pos -> Pos -> Pos -> Bool
between from@(V2 xf yf) to@(V2 xt yt) pos@(V2 a b) =
pos /= from && pos /= to && xm <= a && a <= xM && ym <= b && b <= yM
where
xm = min xf xt
xM = max xf xt
ym = min yf yt
yM = max yf yt
selectVisible :: [(Pos, Pos)] -> [Pos]
selectVisible aList =
unpair . L.filter (\(a, b) -> not . any (between a b) $ nubbed) $ aList
where
nubbed = St.fromList . unpair $ aList
unpair = concatMap (\(a, b) -> [a, b])
allLines :: AstMap -> [(Pos, Pos, Line)]
allLines astMap =
concat .
zipWith
(\a b -> L.map (\x -> (a, x, line a x)) . L.filter (/= a) $ b)
(init astMap) $
tails astMap
grouped :: [(Pos, Pos, Line)] -> MultiSet Pos
grouped =
MS.fromList .
concatMap (selectVisible . L.map (\(a, b, _) -> (a, b))) .
groupBy (\(_, _, a) (_, _, b) -> a == b) .
sortBy (\(_, _, a) (_, _, b) -> compare a b)
findBest :: [(Pos, Pos, Line)] -> Int
findBest = foldOccur maxOccur 0 . grouped
maxOccur :: Pos -> Int -> Int -> Int
maxOccur _ = max
maxByOccur :: Pos -> Int -> (Pos, Int) -> (Pos, Int)
maxByOccur pos occur (bestPos, bestOccur)
| occur > bestOccur = (pos, occur)
| otherwise = (bestPos, bestOccur)
findZapped :: AstMap -> Int
findZapped astMap =
score . zapPos (0, empty) . delete best . St.fromList $ astMap
where
score (V2 x y) = 100 * x + y
linedUp = allLines astMap
best = fst . foldOccur maxByOccur (V2 0 0, 0) . grouped $ linedUp
bestLines =
St.fromList .
L.map (\(_, _, c) -> c) . L.filter (\(a, b, _) -> a == best || b == best) $
linedUp
zapPos preZapped surviving
| length (snd preZapped) == numBet = fst preZapped
| otherwise = zapNeg zapped (difference surviving (snd zapped))
where
zapped =
foldl' (zap best (firstHalf best) surviving) preZapped bestLines
zapNeg preZapped surviving
| length preZapped == numBet = fst preZapped
| otherwise = zapPos zapped (difference surviving (snd zapped))
where
zapped =
foldl' (zap best (secondHalf best) surviving) preZapped bestLines
firstHalf :: Pos -> Pos -> Bool
firstHalf (V2 a b) (V2 c d) = (a == c && d < b) || c > a
secondHalf :: Pos -> Pos -> Bool
secondHalf (V2 a b) (V2 c d) = (a == c && d > b) || c < a
zap ::
Pos -> (Pos -> Bool) -> Set Pos -> (Pos, Set Pos) -> Line -> (Pos, Set Pos)
zap best half surviving (last, zapped) lineOfFire
| size zapped == numBet = (last, zapped)
| otherwise = (new, insert new zapped)
where
inLine = St.filter (aligned lineOfFire half) surviving
new
| null inLine = last
| otherwise =
head . toList . St.filter (\x -> not . any (between best x) $ inLine) $
inLine
buildMap :: String -> AstMap
buildMap =
concat .
zipWith (\a -> L.map (`V2` a)) [0 ..] .
L.map (L.map fst . L.filter ((== '#') . snd) . zip [0 ..]) . lines
part1 :: Bool -> String -> String
part1 _ = show . findBest . allLines . buildMap
part2 :: Bool -> String -> String
part2 _ = show . findZapped . buildMap