23
23
module Main (main ) where
24
24
25
25
import Advent (getInputLines )
26
- import Advent.Coord (above , below , left , origin , right , Coord (.. ))
26
+ import Advent.Coord (Coord (.. ), coordLines )
27
27
import Advent.Memo (memo2 )
28
28
import Data.Map (Map )
29
29
import Data.Map qualified as Map
@@ -36,83 +36,82 @@ import Data.Set qualified as Set
36
36
main :: IO ()
37
37
main =
38
38
do codes <- getInputLines 2024 21
39
- let score n x = (read (init x) * doorInputs n x)
39
+ let score n x = (read (init x) * shortestDoorCodeLength n x)
40
40
print (sum (map (score 2 ) codes))
41
41
print (sum (map (score 25 ) codes))
42
42
43
43
data Pad = Pad (Set Coord ) (Map Char Coord )
44
44
45
- padFromList :: [(Coord , Char )] -> Pad
46
- padFromList xs = Pad (Set. fromList [p | (p, _) <- xs]) (Map. fromList [(c,p) | (p,c) <- xs])
45
+ -- | Turn a list of lines into a 'Pad'. Spaces are removed.
46
+ padFromList :: [String ] -> Pad
47
+ padFromList strs = Pad (Set. fromList (Map. elems buttons)) buttons
48
+ where
49
+ buttons = Map. fromList [(c, p) | (p, c) <- coordLines strs, c /= ' ' ]
47
50
51
+ -- | Find the coordinate of a button.
48
52
padCoord :: Pad -> Char -> Coord
49
53
padCoord (Pad _ m) c = m Map. ! c
50
54
55
+ -- | Test if a coordinate is contained within the pad.
51
56
inPad :: Pad -> Coord -> Bool
52
57
inPad (Pad s _) x = Set. member x s
53
58
54
- -- | The 4-direction pad centered on the @A@ button.
59
+ -- | The 4-direction pad used to control a robot
55
60
robotPad :: Pad
56
- robotPad = padFromList [( C 0 ( - 1 ), ' ^ ' ), ( C 0 0 , ' A ' ), ( C 1 ( - 2 ), ' < ' ), ( C 1 ( - 1 ), ' v ' ), ( C 1 0 , ' > ' ) ]
61
+ robotPad = padFromList [" ^A " , " <v> " ]
57
62
58
- -- | The 10-digit pad centered on the @A@ button.
63
+ -- | The 10-digit pad used to control the door
59
64
doorPad :: Pad
60
- doorPad = padFromList
61
- [ (C (- 3 ) (- 2 ), ' 7' )
62
- , (C (- 3 ) (- 1 ), ' 8' )
63
- , (C (- 3 ) 0 , ' 9' )
64
- , (C (- 2 ) (- 2 ), ' 4' )
65
- , (C (- 2 ) (- 1 ), ' 5' )
66
- , (C (- 2 ) 0 , ' 6' )
67
- , (C (- 1 ) (- 2 ), ' 1' )
68
- , (C (- 1 ) (- 1 ), ' 2' )
69
- , (C (- 1 ) (0 ) , ' 3' )
70
- , (C 0 (- 1 ) , ' 0' )
71
- , (C 0 0 , ' A' )
72
- ]
73
-
74
- doorInputs :: Int -> String -> Int
75
- doorInputs n str =
65
+ doorPad = padFromList [" 789" ," 456" ," 123" ," 0A" ]
66
+
67
+ -- | The length of the shortest input sequence that enters the given
68
+ -- door code via a given number of robot layers.
69
+ shortestDoorCodeLength ::
70
+ Int {- ^ robot layers -} ->
71
+ String {- ^ door code -} ->
72
+ Int {- ^ shortest button press count -}
73
+ shortestDoorCodeLength n str =
76
74
minimum
77
- [ sum (map (robotLength n) keys)
78
- | let deltas = padDeltas doorPad str
79
- , keys <- traverse deltaToKeys deltas
80
- , validate doorPad (concat keys)
75
+ [ sum (map (shortestRobotCodeLength n) keys)
76
+ | keys <- sequence (route doorPad str)
81
77
]
82
78
83
- robotLength :: Int -> String -> Int
84
- robotLength = memo2 \ n str ->
79
+ -- | The length of the shortest input sequence that enters the given
80
+ -- robot directional code via a given number of robot layers.
81
+ shortestRobotCodeLength ::
82
+ Int {- ^ robot layers -} ->
83
+ String {- ^ door code -} ->
84
+ Int {- ^ shortest button press count -}
85
+ shortestRobotCodeLength = memo2 \ n str ->
85
86
if n == 0 then length str else
86
87
minimum
87
- [ sum (map (robotLength (n- 1 )) keys)
88
- | let deltas = padDeltas robotPad str
89
- , keys <- traverse deltaToKeys deltas
90
- , validate robotPad (concat keys)
88
+ [ sum (map (shortestRobotCodeLength (n- 1 )) keys)
89
+ | keys <- sequence (route robotPad str)
91
90
]
92
91
93
- validate :: Pad -> [Char ] -> Bool
94
- validate pad str = all (inPad pad) posns
95
- where
96
- posns = scanl move origin str
97
- move here ' A' = here
98
- move here ' >' = right here
99
- move here ' <' = left here
100
- move here ' ^' = above here
101
- move here ' v' = below here
102
- move _ _ = undefined
103
-
104
- padDeltas :: Pad -> String -> [Coord ]
105
- padDeltas pad str = zipWith (-) (absolutes) (origin: absolutes)
92
+ -- | Find a list of steps needed to input a code on a pad. The inner
93
+ -- lists allow for there to be multiple, valid subsequences that exist
94
+ -- for some keys. Only the most direct routes are considered. This
95
+ -- takes advantage of our input pads only missing corners. Sequences
96
+ -- always start from @A@.
97
+ --
98
+ -- >>> route doorPad "029A"
99
+ -- [["<A"],["^A"],["^^>A",">^^A"],["vvvA"]]
100
+ route :: Pad -> String -> [[String ]]
101
+ route pad str = zipWith (walk pad) (padCoord pad ' A' : absolutes) absolutes
106
102
where
107
103
absolutes = map (padCoord pad) str
108
104
109
- deltaToKeys :: Coord -> [String ]
110
- deltaToKeys (C y x) =
105
+ -- | Find the unique, shortest paths to move from one location to
106
+ -- another on a pad.
107
+ walk :: Pad -> Coord -> Coord -> [String ]
108
+ walk pad (C y1 x1) (C y2 x2) =
111
109
[ keys ++ " A"
112
110
| let rawKeys =
113
- replicate (- y) ' ^' ++
114
- replicate x ' >' ++
115
- replicate y ' v' ++
116
- replicate (- x) ' <'
117
- , keys <- rawKeys : [reverse rawKeys | y /= 0 , x /= 0 ]
111
+ replicate (y1 - y2) ' ^' ++
112
+ replicate (y2 - y1) ' v' ++
113
+ replicate (x2 - x1) ' >' ++
114
+ replicate (x1 - x2) ' <'
115
+ , keys <- [ rawKeys | inPad pad (C y2 x1) ]
116
+ ++ [reverse rawKeys | y1 /= y2, x1 /= x2, inPad pad (C y1 x2)]
118
117
]
0 commit comments