Skip to content

Commit e66dd8b

Browse files
committed
Clean up 2024 day 21 solutions
1 parent 0fe17f7 commit e66dd8b

File tree

1 file changed

+30
-25
lines changed

1 file changed

+30
-25
lines changed

src/main/scala/eu/sim642/adventofcode2024/Day21.scala

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package eu.sim642.adventofcode2024
22

33
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
64
import eu.sim642.adventofcodelib.GridImplicits.*
75
import eu.sim642.adventofcodelib.box.Box
6+
import eu.sim642.adventofcodelib.graph.*
7+
import eu.sim642.adventofcodelib.pos.Pos
88

99
import 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

Comments
 (0)