-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay12.hs
81 lines (66 loc) · 2.63 KB
/
Day12.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
import Data.Function (on)
import Data.List (groupBy, intersperse, maximumBy, minimumBy, nub, partition, singleton, sortBy)
import Data.Map ((!))
import Data.Map qualified as M
import Data.Maybe (catMaybes)
import Data.Ord (comparing)
import Data.Set qualified as S
import Debug.Trace (trace)
main = readFile "input12.txt" >>= print . solve . parse
-- main = readFile "test12.txt" >>= print . solve . parse
parse = toGrid . lines
toGrid xs = do
(y, row) <- zip [0 ..] xs
(x, c) <- zip [0 ..] row
return ((x, y), c)
solve points =
sum
$ map
( \(_, points) ->
let area = length points
perimeter = length $ concat $ M.elems $ perim points
in area * sides points
)
$ zones
$ M.fromList points
groupAdj :: S.Set (Int, Int) -> [S.Set (Int, Int)]
groupAdj all =
case S.minView all of
Just (x, _) ->
let adj = allAdjacent (S.singleton x) all
in adj : groupAdj (all S.\\ adj)
Nothing -> []
sides points = sum $ map (length . groupAdj . S.fromList) $ M.elems p
where
p = perim points
adj (x0, y0) (x1, y1) = x0 == x1 || y0 == y1
zones :: M.Map (Int, Int) Char -> [(Char, S.Set (Int, Int))]
zones grid =
case M.minViewWithKey grid of
Just ((pos, char), grid') ->
let positions = zone grid' (pos, char)
in (char, positions) : zones (M.withoutKeys grid positions)
Nothing -> []
dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
zone :: M.Map (Int, Int) Char -> ((Int, Int), Char) -> S.Set (Int, Int)
zone grid (pos, c) = allAdjacent (S.singleton pos) (S.fromList $ map fst $ filter ((== c) . snd) $ M.toList grid)
allAdjacent :: S.Set (Int, Int) -> S.Set (Int, Int) -> S.Set (Int, Int)
allAdjacent ok toCheck =
case S.partition (isAdjacentToAny ok) toCheck of
(new, old) | S.null new || S.null old -> S.union ok new
(new, old) -> allAdjacent (S.union ok new) old
(x, y) +: (a, b) = (x + a, b + y)
isAdjacentToAny ok pos = any (\pos -> S.member pos ok) (neighbors pos)
where
neighbors pos = map (+: pos) dirs
perim :: S.Set (Int, Int) -> M.Map (Int, Int) [(Int, Int)]
perim points = go [S.findMin points] M.empty S.empty
where
go :: [(Int, Int)] -> M.Map (Int, Int) [(Int, Int)] -> S.Set (Int, Int) -> M.Map (Int, Int) [(Int, Int)]
go [] acc _ = acc
go (pos : q) acc visited =
let optim xy = xy `notElem` q && S.notMember xy visited
onShape xy = S.member xy points
(ok, ko) = partition (onShape . snd) $ filter (optim . snd) $ zipWith (\p v -> (v, p +: v)) (repeat pos) dirs
acc' = M.unionWith (++) acc $ M.fromListWith (++) $ map (\(k, v) -> (k, [v])) ko
in go (q ++ map snd ok) acc' (S.insert pos visited)