11package eu .sim642 .adventofcode2024
22
33import eu .sim642 .adventofcodelib .Grid
4- import eu .sim642 .adventofcodelib .graph .{BFS , GraphSearch , GraphTraversal , Heuristic , SimultaneousBFS , TargetNode , UnitNeighbors }
5- import eu .sim642 .adventofcodelib .pos .Pos
64import eu .sim642 .adventofcodelib .GridImplicits .*
75import eu .sim642 .adventofcodelib .box .Box
6+ import eu .sim642 .adventofcodelib .graph .*
7+ import eu .sim642 .adventofcodelib .pos .Pos
88
99import scala .collection .mutable
1010
@@ -46,7 +46,7 @@ object Day21 {
4646
4747 case class State (directionalPoss : List [Pos ], numericPos : Pos , input : Code ) {
4848
49- def numericPress (button : Char ): Option [State ] = button match {
49+ private def numericPress (button : Char ): Option [State ] = button match {
5050 case 'A' =>
5151 val newButton = numericKeypad(numericPos)
5252 Some (copy(input = input + newButton))
@@ -59,7 +59,7 @@ object Day21 {
5959 None // out of keypad
6060 }
6161
62- def directionalPress (button : Char ): Option [State ] = directionalPoss match {
62+ private def directionalPress (button : Char ): Option [State ] = directionalPoss match {
6363 case Nil => numericPress(button)
6464 case directionalPos :: newDirectionalPoss =>
6565 button match {
@@ -84,9 +84,11 @@ object Day21 {
8484 override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
8585
8686 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' ), " " )
8889
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))
9092
9193 override def isTargetNode (state : State , dist : Int ): Boolean = state.input == code
9294 }
@@ -110,14 +112,18 @@ object Day21 {
110112 }
111113 }
112114
115+ private val offsetDirectionals : Map [Pos , Char ] = directionalOffsets.map(_.swap)
116+
113117 private def keypadPaths (keypad : Grid [Char ]): Map [(Char , Char ), Set [Code ]] = {
114118 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
115120 (for {
116121 startPos <- box.iterator
117122 if keypad(startPos) != ' '
118123 targetPos <- box.iterator
119124 if keypad(targetPos) != ' '
120125 } yield {
126+ // TODO: use multi-predecessor BFS to construct all shortest paths
121127 val graphSearch = new GraphSearch [Pos ] with UnitNeighbors [Pos ] with TargetNode [Pos ] {
122128 override val startNode : Pos = startPos
123129
@@ -132,7 +138,7 @@ object Day21 {
132138 .filter(_.head == targetPos)
133139 .map(poss =>
134140 (poss lazyZip poss.tail)
135- .map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 })
141+ .map({ (p2, p1) => offsetDirectionals( p1 - p2) })
136142 .mkString
137143 )
138144 .toSet
@@ -142,26 +148,25 @@ object Day21 {
142148 private val numericPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(numericKeypad)
143149 private val directionalPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(directionalKeypad)
144150
151+ def keypadPaths (keypad : Int ): Map [(Char , Char ), Set [Code ]] = if (keypad == 0 ) numericPaths else directionalPaths
152+
145153 override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
146154 val memo = mutable.Map .empty[(Code , Int ), Long ]
147155
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+ }
165170 }
166171
167172 helper(code, 0 )
@@ -176,7 +181,7 @@ object Day21 {
176181 val part2DirectionalKeypads = 25
177182
178183 def main (args : Array [String ]): Unit = {
179- import DynamicProgrammingSolution ._
184+ import DynamicProgrammingSolution .*
180185 println(sumCodeComplexity(parseCodes(input), part1DirectionalKeypads))
181186 println(sumCodeComplexity(parseCodes(input), part2DirectionalKeypads))
182187
0 commit comments