-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.hs
146 lines (125 loc) · 3.87 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
141
142
143
144
145
146
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
import AoC
import AoC.Grid
import Control.Monad (guard)
import Data.Foldable (find)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
type N = Int
type SetGrid = HashSet (N, N)
type Bounds = ((N, N), (N, N))
getBounds :: SetGrid -> Bounds
getBounds g =
let (rs, cs) = unzip $ HashSet.toList g
in ((minimum rs, minimum cs), (maximum rs, maximum cs))
parseAll :: String -> SetGrid
parseAll =
HashMap.keysSet
. HashMap.filter (== '#')
. parseMapGrid id
data Dir = N | S | W | E
deriving (Show, Eq, Ord, Enum, Bounded)
next :: (Eq a, Enum a, Bounded a) => a -> a
next d | d == maxBound = minBound
| otherwise = succ d
setup :: SetGrid -> HashSet (V2 N)
setup = HashSet.map v2
dirv :: Dir -> V2 N
dirv =
\case N -> v2 ( 0, -1)
S -> v2 ( 0, 1)
W -> v2 (-1, 0)
E -> v2 ( 1, 0)
dirNeighbors :: Dir -> [V2 N]
dirNeighbors =
\case N -> [ v2 (-1, -1)
, v2 ( 0, -1)
, v2 ( 1, -1)
]
S -> [ v2 (-1, 1)
, v2 ( 0, 1)
, v2 ( 1, 1)
]
W -> [ v2 (-1, 1)
, v2 (-1, 0)
, v2 (-1, -1)
]
E -> [ v2 (1, 1)
, v2 (1, 0)
, v2 (1, -1)
]
freeNeighbors :: HashSet (V2 N) -> V2 N -> [V2 N]
freeNeighbors g pos =
filter (not . (`HashSet.member` g))
. map ((+ pos) . v2)
$ [ ( 0, 1)
, ( 0, -1)
, ( 1, 0)
, ( 1, 1)
, ( 1, -1)
, (-1, 0)
, (-1, 1)
, (-1, -1)
]
elfPropose :: HashSet (V2 N) -> V2 N -> Dir -> Maybe (Dir, V2 N)
elfPropose g pos dir = do
let !free = freeNeighbors g pos
guard $ length free /= 8
let options = take 4 $ iterate next dir
proposed <- find (\d -> all (`elem` free) (map (+ pos) (dirNeighbors d))) options
pure (proposed, pos + dirv proposed)
propose :: HashSet (V2 N) -> Dir -> HashSet (V2 N)
propose g dir = go HashSet.empty HashMap.empty HashSet.empty (HashSet.toList g)
where go !bad !seen !new =
\case [] -> new
!pos:rest ->
case elfPropose g pos dir of
Just (_, !pos')
| pos' `HashSet.member` bad ->
go bad
seen
(HashSet.insert pos new)
rest
| Just conflict <- seen HashMap.!? pos' ->
go (HashSet.insert pos' bad)
(HashMap.delete pos' seen)
(HashSet.insert pos
. HashSet.insert conflict
. HashSet.delete pos'
$ new)
rest
| otherwise ->
go bad
(HashMap.insert pos' pos seen)
(HashSet.insert pos' new)
rest
Nothing -> go bad seen (HashSet.insert pos new) rest
step :: (HashSet (V2 N), Dir) -> (HashSet (V2 N), Dir)
step (g, dir) =
(propose g dir, next dir)
empty :: HashSet (V2 N) -> Int
empty g =
let ((xmin, ymin), (xmax, ymax)) = getBounds $ HashSet.map (\(V2 t) -> t) g
in
(1 + xmax - xmin) * (1 + ymax - ymin) - fromIntegral (length g)
part1 :: SetGrid -> Int
part1 g =
empty . fst $ iterate step (setup g, N) !! 10
part2 :: SetGrid -> Int
part2 g =
fst $ fixpointiOn fst step (setup g, N)
main :: IO ()
main = main' "input.txt"
exampleMain :: IO ()
exampleMain = main' "example.txt"
smallMain :: IO ()
smallMain = main' "small.txt"
main' :: FilePath -> IO ()
main' file = do
input <- parseAll <$> readFile file
print (part1 input)
print (part2 input)