1
1
package eu .sim642 .adventofcode2024
2
2
3
3
import eu .sim642 .adventofcodelib .Grid
4
- import eu .sim642 .adventofcodelib .graph .{BFS , GraphSearch , GraphTraversal , Heuristic , SimultaneousBFS , TargetNode , UnitNeighbors }
5
- import eu .sim642 .adventofcodelib .pos .Pos
6
4
import eu .sim642 .adventofcodelib .GridImplicits .*
7
5
import eu .sim642 .adventofcodelib .box .Box
6
+ import eu .sim642 .adventofcodelib .graph .*
7
+ import eu .sim642 .adventofcodelib .pos .Pos
8
8
9
9
import scala .collection .mutable
10
10
@@ -46,7 +46,7 @@ object Day21 {
46
46
47
47
case class State (directionalPoss : List [Pos ], numericPos : Pos , input : Code ) {
48
48
49
- def numericPress (button : Char ): Option [State ] = button match {
49
+ private def numericPress (button : Char ): Option [State ] = button match {
50
50
case 'A' =>
51
51
val newButton = numericKeypad(numericPos)
52
52
Some (copy(input = input + newButton))
@@ -59,7 +59,7 @@ object Day21 {
59
59
None // out of keypad
60
60
}
61
61
62
- def directionalPress (button : Char ): Option [State ] = directionalPoss match {
62
+ private def directionalPress (button : Char ): Option [State ] = directionalPoss match {
63
63
case Nil => numericPress(button)
64
64
case directionalPos :: newDirectionalPoss =>
65
65
button match {
@@ -84,9 +84,11 @@ object Day21 {
84
84
override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
85
85
86
86
val graphSearch = new GraphSearch [State ] with UnitNeighbors [State ] {
87
- override val startNode : State = State (List .fill(directionalKeypads)(directionalKeypad.posOf('A' )), numericKeypad.posOf('A' ), " " )
87
+ override val startNode : State =
88
+ State (List .fill(directionalKeypads)(directionalKeypad.posOf('A' )), numericKeypad.posOf('A' ), " " )
88
89
89
- override def unitNeighbors (state : State ): IterableOnce [State ] = " <v>^A" .iterator.flatten(state.userPress).filter(s => code.startsWith(s.input))
90
+ override def unitNeighbors (state : State ): IterableOnce [State ] =
91
+ " <v>^A" .iterator.flatten(state.userPress).filter(newState => code.startsWith(newState.input))
90
92
91
93
override def isTargetNode (state : State , dist : Int ): Boolean = state.input == code
92
94
}
@@ -110,14 +112,18 @@ object Day21 {
110
112
}
111
113
}
112
114
115
+ private val offsetDirectionals : Map [Pos , Char ] = directionalOffsets.map(_.swap)
116
+
113
117
private def keypadPaths (keypad : Grid [Char ]): Map [(Char , Char ), Set [Code ]] = {
114
118
val box = Box (Pos .zero, Pos (keypad(0 ).size - 1 , keypad.size - 1 ))
119
+ // TODO: use one traversal per position (to all other positions), instead of pairwise
115
120
(for {
116
121
startPos <- box.iterator
117
122
if keypad(startPos) != ' '
118
123
targetPos <- box.iterator
119
124
if keypad(targetPos) != ' '
120
125
} yield {
126
+ // TODO: use multi-predecessor BFS to construct all shortest paths
121
127
val graphSearch = new GraphSearch [Pos ] with UnitNeighbors [Pos ] with TargetNode [Pos ] {
122
128
override val startNode : Pos = startPos
123
129
@@ -132,7 +138,7 @@ object Day21 {
132
138
.filter(_.head == targetPos)
133
139
.map(poss =>
134
140
(poss lazyZip poss.tail)
135
- .map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 })
141
+ .map({ (p2, p1) => offsetDirectionals( p1 - p2) })
136
142
.mkString
137
143
)
138
144
.toSet
@@ -142,26 +148,25 @@ object Day21 {
142
148
private val numericPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(numericKeypad)
143
149
private val directionalPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(directionalKeypad)
144
150
151
+ def keypadPaths (keypad : Int ): Map [(Char , Char ), Set [Code ]] = if (keypad == 0 ) numericPaths else directionalPaths
152
+
145
153
override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
146
154
val memo = mutable.Map .empty[(Code , Int ), Long ]
147
155
148
- def helper (code : Code , i : Int ): Long = {
149
- memo.getOrElseUpdate((code, i), {
150
- // assert(directionalKeypads == 0)
151
- code.foldLeft(('A' , 0L ))({ case ((prev, length), cur) =>
152
- val newLength =
153
- (for {
154
- path <- if (i == 0 ) numericPaths((prev, cur)) else directionalPaths((prev, cur))
155
- path2 = path + 'A'
156
- len =
157
- if (i == directionalKeypads)
158
- path2.length.toLong
159
- else
160
- helper(path2, i + 1 )
161
- } yield len).min
162
- (cur, length + newLength)
163
- })._2
164
- })
156
+ def helper (code : Code , keypad : Int ): Long = {
157
+ if (keypad == directionalKeypads + 1 )
158
+ code.length
159
+ else {
160
+ memo.getOrElseUpdate((code, keypad), {
161
+ ((" A" + code) lazyZip code) // start moving from A
162
+ .map({ (prev, cur) =>
163
+ keypadPaths(keypad)((prev, cur))
164
+ .map(path => helper(path + 'A' , keypad + 1 )) // end at A to press
165
+ .min
166
+ })
167
+ .sum
168
+ })
169
+ }
165
170
}
166
171
167
172
helper(code, 0 )
@@ -176,7 +181,7 @@ object Day21 {
176
181
val part2DirectionalKeypads = 25
177
182
178
183
def main (args : Array [String ]): Unit = {
179
- import DynamicProgrammingSolution ._
184
+ import DynamicProgrammingSolution .*
180
185
println(sumCodeComplexity(parseCodes(input), part1DirectionalKeypads))
181
186
println(sumCodeComplexity(parseCodes(input), part2DirectionalKeypads))
182
187
0 commit comments