- Holes are no longer allowed when building executables - Stack overflow in mapMaybe (Day15)
111 lines
3.5 KiB
Agda
111 lines
3.5 KiB
Agda
module Day20
|
||
|
||
import Prelude
|
||
import Node
|
||
import Aoc
|
||
import SortedMap
|
||
|
||
gridPoints : String → List (Char × Int × Int)
|
||
gridPoints text = go 0 0 (unpack text) Nil
|
||
where
|
||
-- might as well be tail recursive
|
||
go : Int → Int → List Char → List (Char × Int × Int) → List (Char × Int × Int)
|
||
go row col Nil points = points
|
||
go row col ('\n' :: cs) points = go (row + 1) 0 cs points
|
||
go row col (c :: cs) points = go row (col + 1) cs ((c,row,col) :: points)
|
||
|
||
State Grid Next Cheat : U
|
||
Grid = SortedMap Point Char
|
||
State = SortedMap Point Int
|
||
Next = (Point × Int)
|
||
Cheat = (Point × Point)
|
||
|
||
data Dir : U where North East South West : Dir
|
||
|
||
dirs : List Dir
|
||
dirs = (North :: South :: East :: West :: Nil)
|
||
|
||
move : Point → Dir → Point
|
||
move (r, c) North = (r - 1, c)
|
||
move (r, c) East = (r, c + 1)
|
||
move (r, c) South = (r + 1, c)
|
||
move (r, c) West = (r, c - 1)
|
||
|
||
distances : SortedMap Point Char → Point → State
|
||
distances grid start = go EmptyMap ((start,0) :: Nil) Lin
|
||
where
|
||
valid : State → Point → Bool
|
||
valid st pt = case lookupMap pt grid of
|
||
Just (_, '.') => case lookupMap pt st of
|
||
Just _ => False
|
||
Nothing => True
|
||
_ => False
|
||
|
||
go : State → List (Point × Int) → SnocList (Point × Int) → State
|
||
go st Nil Lin = st
|
||
go st xs Lin = go st Nil (Lin <>< xs)
|
||
go st xs (ys :< (pt, l)) =
|
||
let next = map (flip _,_ $ l + 1) $ filter (valid st) $ map (move pt) dirs
|
||
st = foldMap const st next
|
||
in go st (next ++ xs) ys
|
||
|
||
manh : Point → Point → Int
|
||
manh (r,c) (r',c') =
|
||
let dr = if r < r' then r' - r else r - r'
|
||
dc = if c < c' then c' - c else c - c'
|
||
in dr + dc
|
||
|
||
candidates : Point → Int → List Point
|
||
candidates pt@(sr,sc) dist = go (sr - dist) (sc - dist) Nil
|
||
where
|
||
go : Int → Int → List Point → List Point
|
||
go r' c' acc =
|
||
if sr + dist < r' then acc
|
||
else if sc + dist < c' then go (r' + 1) (sc - dist) acc
|
||
else if manh pt (r',c') <= dist
|
||
then go r' (c' + 1) ((r', c') :: acc)
|
||
else go r' (c' + 1) acc
|
||
|
||
calcCheats : State → State → Int → Int → Int
|
||
calcCheats start dists fuel threshold = go (toList start) 0
|
||
where
|
||
-- only '.' have dist
|
||
tryCand : Int → Point → Point → Int
|
||
tryCand l pt cand = case lookupMap cand dists of
|
||
Just (_, dist) => if l + manh pt cand + dist <= threshold then 1 else 0
|
||
Nothing => 0
|
||
|
||
go : List (Point × Int) → Int → Int
|
||
go Nil acc = acc
|
||
go (x@(pt,l) :: xs) acc =
|
||
let acc = foldl _+_ acc $ map (tryCand l pt) $ candidates pt fuel
|
||
in go xs acc
|
||
|
||
run : String → Int → IO Unit
|
||
run fn time = do
|
||
putStrLn fn
|
||
text <- readFile fn
|
||
let pts = map swap $ gridPoints text
|
||
let grid = foldMap const EmptyMap pts
|
||
let (Just (start,_)) = find (_==_ 'S' ∘ snd) pts | _ => putStrLn "no start"
|
||
let (Just (end,_)) = find (_==_ 'E' ∘ snd) pts | _ => putStrLn "no end"
|
||
let grid' = updateMap start '.' $ updateMap end '.' grid
|
||
let dists = distances grid' end
|
||
-- this bit threw me off for a while
|
||
let dists = updateMap end 0 dists
|
||
let dists' = distances grid' start
|
||
let dists' = updateMap start 0 dists'
|
||
let (Just (_,base)) = lookupMap start dists | _ => putStrLn "no start in dists"
|
||
let p1 = calcCheats dists dists' 2 (base - time)
|
||
|
||
putStrLn $ "base " ++ show base
|
||
putStrLn $ "part1 " ++ show p1
|
||
let p2 = calcCheats dists dists' 20 (base - time)
|
||
putStrLn $ "part2 " ++ show p2
|
||
pure MkUnit
|
||
|
||
main : IO Unit
|
||
main = do
|
||
run "aoc2024/day20/eg.txt" 50
|
||
run "aoc2024/day20/input.txt" 100
|