Skip to content

Commit c1f3b32

Browse files
committed
Solve 2024 day 21 part 2
1 parent f2e2abd commit c1f3b32

File tree

2 files changed

+97
-8
lines changed

2 files changed

+97
-8
lines changed

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

+86-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package eu.sim642.adventofcode2024
22

33
import eu.sim642.adventofcodelib.Grid
4-
import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, Heuristic, UnitNeighbors}
4+
import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, GraphTraversal, Heuristic, SimultaneousBFS, TargetNode, UnitNeighbors}
55
import eu.sim642.adventofcodelib.pos.Pos
66
import eu.sim642.adventofcodelib.GridImplicits.*
7+
import eu.sim642.adventofcodelib.box.Box
8+
9+
import scala.collection.mutable
710

811
object Day21 {
912

@@ -78,18 +81,96 @@ object Day21 {
7881
BFS.search(graphSearch).target.get._2
7982
}
8083

81-
def codeComplexity(code: Code): Int = {
84+
85+
// copied & modified from 2024 day 10
86+
// TODO: extract to library?
87+
def pathSearch[A](graphSearch: GraphSearch[A] & UnitNeighbors[A]): GraphSearch[List[A]] & UnitNeighbors[List[A]] = {
88+
new GraphSearch[List[A]] with UnitNeighbors[List[A]] {
89+
override val startNode: List[A] = List(graphSearch.startNode)
90+
91+
override def unitNeighbors(node: List[A]): IterableOnce[List[A]] =
92+
graphSearch.unitNeighbors(node.head).iterator.map(_ :: node)
93+
94+
override def isTargetNode(node: List[A], dist: Int): Boolean = graphSearch.isTargetNode(node.head, dist)
95+
}
96+
}
97+
98+
private def keypadPaths(keypad: Grid[Char]): Map[(Char, Char), Set[Code]] = {
99+
val box = Box(Pos.zero, Pos(keypad(0).size - 1, keypad.size - 1))
100+
(for {
101+
startPos <- box.iterator
102+
if keypad(startPos) != ' '
103+
targetPos <- box.iterator
104+
if keypad(targetPos) != ' '
105+
} yield {
106+
val graphSearch = new GraphSearch[Pos] with UnitNeighbors[Pos] with TargetNode[Pos] {
107+
override val startNode: Pos = startPos
108+
109+
override def unitNeighbors(pos: Pos): IterableOnce[Pos] =
110+
Pos.axisOffsets.map(pos + _).filter(keypad.containsPos).filter(keypad(_) != ' ')
111+
112+
override val targetNode: Pos = targetPos
113+
}
114+
(keypad(targetPos), keypad(startPos)) -> // flipped because paths are reversed
115+
SimultaneousBFS.search(pathSearch(graphSearch))
116+
.nodes
117+
.filter(_.head == targetPos)
118+
.map(poss =>
119+
(poss lazyZip poss.tail)
120+
.map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 })
121+
.mkString
122+
)
123+
.toSet
124+
}).toMap
125+
}
126+
127+
private val numericPaths: Map[(Char, Char), Set[Code]] = keypadPaths(numericKeypad)
128+
private val directionalPaths: Map[(Char, Char), Set[Code]] = keypadPaths(directionalKeypad)
129+
130+
//println(numericPaths)
131+
132+
def shortestSequenceLength2(code: Code, directionalKeypads: Int, i: Int = 0): Long = {
133+
134+
val memo = mutable.Map.empty[(Code, Int), Long]
135+
136+
def helper(code: Code, i: Int): Long = {
137+
memo.getOrElseUpdate((code, i), {
138+
//assert(directionalKeypads == 0)
139+
code.foldLeft(('A', 0L))({ case ((prev, length), cur) =>
140+
val newLength =
141+
(for {
142+
path <- if (i == 0) numericPaths((prev, cur)) else directionalPaths((prev, cur))
143+
path2 = path + 'A'
144+
len =
145+
if (i == directionalKeypads)
146+
path2.length.toLong
147+
else
148+
helper(path2, i + 1)
149+
} yield len).min
150+
(cur, length + newLength)
151+
})._2
152+
})
153+
}
154+
155+
helper(code, 0)
156+
}
157+
158+
159+
def codeComplexity(code: Code, directionalKeypads: Int): Long = {
82160
val numericPart = code.dropRight(1).toInt
83-
shortestSequenceLength(code) * numericPart
161+
shortestSequenceLength2(code, directionalKeypads) * numericPart
84162
}
85163

86-
def sumCodeComplexity(codes: Seq[Code]): Int = codes.map(codeComplexity).sum
164+
def sumCodeComplexity(codes: Seq[Code], directionalKeypads: Int): Long = codes.map(codeComplexity(_, directionalKeypads)).sum
87165

88166
def parseCodes(input: String): Seq[Code] = input.linesIterator.toSeq
89167

90168
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day21.txt")).mkString.trim
91169

92170
def main(args: Array[String]): Unit = {
93-
println(sumCodeComplexity(parseCodes(input)))
171+
println(sumCodeComplexity(parseCodes(input), 2))
172+
println(sumCodeComplexity(parseCodes(input), 25))
173+
174+
// part 2: 1301407762 - too low (Int overflowed in shortestSequenceLength2)
94175
}
95176
}

src/test/scala/eu/sim642/adventofcode2024/Day21Test.scala

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,18 @@ class Day21Test extends AnyFunSuite {
1313
|379A""".stripMargin
1414

1515
test("Part 1 examples") {
16-
assert(sumCodeComplexity(parseCodes(exampleInput)) == 126384)
16+
assert(shortestSequenceLength2("029A", 0) == "<A^A>^^AvvvA".length)
17+
assert(shortestSequenceLength2("029A", 1) == "v<<A>>^A<A>AvA<^AA>A<vAAA>^A".length)
18+
assert(shortestSequenceLength2("029A", 2) == "<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A".length)
19+
20+
assert(sumCodeComplexity(parseCodes(exampleInput), 2) == 126384)
21+
}
22+
23+
test("Part 1 input answer") {
24+
assert(sumCodeComplexity(parseCodes(input), 2) == 157892)
1725
}
1826

19-
test("Part 1 input") {
20-
assert(sumCodeComplexity(parseCodes(input)) == 157892)
27+
test("Part 2 input answer") {
28+
assert(sumCodeComplexity(parseCodes(input), 25) == 197015606336332L)
2129
}
2230
}

0 commit comments

Comments
 (0)