-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.hs
93 lines (80 loc) · 2.66 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
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
import AoC.Grid
import Control.Monad (guard)
import Data.Foldable (find)
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
type Pos = (Int, Int)
type Input = (HashSet Pos, Pos, Pos)
parseAll :: String -> Input
parseAll input =
let g = parseMapGrid id input
Just (start, _) = find ((== 'S') . snd) $ HashMap.toList g
Just (end, _) = find ((== 'E') . snd) $ HashMap.toList g
in
( HashMap.keysSet $ HashMap.filter (== '#') g
, start
, end)
(|+|) :: Pos -> Pos -> Pos
(x, y) |+| (dx, dy) = (x + dx, y + dy)
dirs :: [Pos]
dirs = [ ( 1, 0)
, (-1, 0)
, ( 0, 1)
, ( 0, -1)
]
distances :: Hashable node => (node -> [node]) -> node -> HashMap node Int
distances next start = go (HashMap.singleton start 0) [start]
where go visited =
\case [] -> visited
n:ns ->
let nbhd = next n
c = visited HashMap.! n
nexts = HashMap.fromList $ map (, c + 1) nbhd
visited' = HashMap.unionWith min visited nexts
toVisit = HashMap.difference nexts visited
in go visited' (HashMap.keys toVisit <> ns)
neighbors :: HashSet Pos -> Pos -> [Pos]
neighbors g n =
filter (not . (`HashSet.member` g))
$ map (n |+|) dirs
dist :: Pos -> Pos -> Int
dist (x1, y1) (x2, y2) = abs (x1 - x2) + abs (y1 - y2)
findCheats :: HashMap Pos Int
-> HashMap Pos Int
-> Int
-> Int
-> [(Pos, Pos)]
findCheats fromStart fromEnd target maxTime = pairs
where pairs = do
(cstart, cstartCost) <- HashMap.toList fromStart
guard $ cstartCost < target
(cend, cendCost) <- HashMap.toList fromEnd
let d = dist cstart cend
cost = cstartCost + cendCost + d
guard $ 0 < d
&& d <= maxTime
&& cost <= target
pure (cstart, cend)
parts :: Int -> Input -> [Int]
parts limit (g, s, e) =
let !fromStart = distances (neighbors g) s
!fromEnd = distances (neighbors g) e
l = fromStart HashMap.! e
target = l - limit
in map (length . findCheats fromStart fromEnd target) [2, 20]
main :: IO ()
main = main' 100 "input.txt"
exampleMain :: IO ()
exampleMain = main' 64 "example.txt"
main' :: Int -> FilePath -> IO ()
main' limit file = do
input <- parseAll <$> readFile file
mapM_ print (parts limit input)