module Day21 import Prelude import Node import Aoc import SortedMap import Parser min : Int → Int → Int min a b = if a < b then a else b 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) 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) Costs : U Costs = SortedMap (Point × Point) Int -- linked list of keypads record Keypad where constructor KP name : String start : Point interdit : Point costs : Costs -- cache of costs next : Maybe Keypad getPaths : Point → Point → Point → List (List Dir) getPaths interdit pt@(a,b) to@(c,d) = if pt == to then Nil :: Nil else if pt == interdit then Nil else join $ map check dirs where check : Dir → List (List Dir) check North = if c < a then map (_::_ North) $ getPaths interdit (move pt North) (c,d) else Nil check South = if a < c then map (_::_ South) $ getPaths interdit (move pt South) (c,d) else Nil check East = if b < d then map (_::_ East) $ getPaths interdit (move pt East) (c,d) else Nil check West = if d < b then map (_::_ West) $ getPaths interdit (move pt West) (c,d) else Nil updateCost : Point × Point → Int → Keypad → Keypad updateCost path cost (KP n s i c nxt) = (KP n s i (updateMap path cost c) nxt) keyPos : Dir → Point keyPos North = (0,1) keyPos South = (1,1) keyPos East = (1,2) keyPos West = (1,0) -- cost to run a path in a keypad pathCost : Point → Point → Keypad → Keypad × Int -- cost of sequence of points (run in parent keypad) -- for numpad, we pick points from the map, for the rest map keyPos ... seqCost : Point → List Point → Keypad × Int → Keypad × Int seqCost cur Nil (kp, cost) = (kp, cost) seqCost cur (pt :: pts) (kp, cost) = let (kp, cost') = pathCost cur pt kp in seqCost pt pts (kp, cost + cost') -- cost of best path from -> to in kp pathCost from to kp = case lookupMap (from, to) kp.costs of Just (_, cost) => (kp, cost) Nothing => let (path :: paths) = getPaths kp.interdit from to | _ => ? in case kp of (KP n s i c Nothing) => (kp, 1) (KP n s i c (Just kp')) => let (kp', cost) = mincost path paths kp' in let kp = KP n s i c (Just kp') in (updateCost (from,to) cost kp, cost) where xlate : List Dir → Point -> List Point xlate Nil a = a :: Nil xlate (d :: ds) a = keyPos d :: xlate ds a mincost : List Dir → List (List Dir) → Keypad → Keypad × Int mincost path paths kp = let (kp', cost) = seqCost (0,2) (xlate path kp.start) (kp, 0) in case paths of Nil => (kp', cost) (path :: paths) => let (kp', cost') = mincost path paths kp' in (kp', min cost cost') fromList : ∀ k v. {{Ord k}} {{Eq k}} → List (k × v) → SortedMap k v fromList xs = foldMap (\ a b => b) EmptyMap xs getNum : String → Int getNum str = case number (unpack str) of Right (n, _) => n _ => 0 runOne : Keypad → SortedMap Char Point → String → Int × Int runOne kp numpad str = let pts = map snd $ mapMaybe (flip lookupMap numpad) $ unpack str in let res = seqCost (3,2) pts (kp, 0) in (getNum str, snd res) makeKeypad : Int → Keypad -> Keypad makeKeypad 0 kp = kp makeKeypad n kp = makeKeypad (n - 1) $ KP (show n) (0,2) (0,0) EmptyMap (Just kp) run : String -> IO Unit run fn = do putStrLn fn text <- readFile fn let codes = split (trim text) "\n" -- the space is illegal spot let numpad = fromList $ filter (not ∘ _==_ ' ' ∘ fst) $ gridPoints "789\n456\n123\n 0A" debugLog $ toList numpad let rob1 = KP "r1" (0,2) (0,0) EmptyMap Nothing let robn = makeKeypad 2 rob1 let kp = KP "kp" (3,2) (3,0) EmptyMap (Just robn) let p1 = foldl _+_ 0 $ map (uncurry _*_ ∘ runOne kp numpad) codes putStrLn $ "part1 " ++ show p1 let rob1 = KP "r1" (0,2) (0,0) EmptyMap Nothing let robn = makeKeypad 25 rob1 let kp = KP "kp" (3,2) (3,0) EmptyMap (Just robn) let p2 = foldl _+_ 0 $ map (uncurry _*_ ∘ runOne kp numpad) codes putStrLn $ "part2 " ++ show p2 main : IO Unit main = do run "aoc2024/day21/eg.txt" run "aoc2024/day21/input.txt"