Skip to content

Commit 4fc4c42

Browse files
authored
Create day21.scala (#786)
1 parent 0a09b49 commit 4fc4c42

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

2024/src/day21.scala

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package day21
2+
3+
import locations.Directory.currentDir
4+
import inputs.Input.loadFileSync
5+
6+
def loadInput(): String = loadFileSync(s"$currentDir/../input/day21")
7+
8+
case class Pos(x: Int, y: Int):
9+
def +(other: Pos) = Pos(x + other.x, y + other.y)
10+
def -(other: Pos) = Pos(x - other.x, y - other.y)
11+
def projX = Pos(x, 0)
12+
def projY = Pos(0, y)
13+
14+
val numericalKeypad = Map(
15+
'7' -> Pos(0, 0), '8' -> Pos(1, 0), '9' -> Pos(2, 0),
16+
'4' -> Pos(0, 1), '5' -> Pos(1, 1), '6' -> Pos(2, 1),
17+
'1' -> Pos(0, 2), '2' -> Pos(1, 2), '3' -> Pos(2, 2),
18+
'0' -> Pos(1, 3), 'A' -> Pos(2, 3),
19+
)
20+
val numericalKeypadPositions = numericalKeypad.values.toSet
21+
22+
val directionalKeypad = Map(
23+
'^' -> Pos(1, 0), 'A' -> Pos(2, 0),
24+
'<' -> Pos(0, 1), 'v' -> Pos(1, 1), '>' -> Pos(2, 1),
25+
)
26+
val directionalKeypadPositions = directionalKeypad.values.toSet
27+
28+
29+
/**********/
30+
/* Part 1 */
31+
/**********/
32+
33+
def minPathStep(from: Pos, to: Pos, positions: Set[Pos]): String =
34+
val shift = to - from
35+
val h = (if shift.x > 0 then ">" else "<") * shift.x.abs
36+
val v = (if shift.y > 0 then "v" else "^") * shift.y.abs
37+
val reverse = !positions(from + shift.projX) || (positions(from + shift.projY) && shift.x > 0)
38+
if reverse then v + h + 'A' else h + v + 'A'
39+
40+
def minPath(input: String, isNumerical: Boolean = false): String =
41+
val keypad = if isNumerical then numericalKeypad else directionalKeypad
42+
val positions = if isNumerical then numericalKeypadPositions else directionalKeypadPositions
43+
(s"A$input").map(keypad).sliding(2).map(p => minPathStep(p(0), p(1), positions)).mkString
44+
45+
def part1(input: String): Long =
46+
input
47+
.linesIterator
48+
.filter(_.nonEmpty)
49+
.map: line => // 029A
50+
val path1 = minPath(line, isNumerical = true) // <A^A^^>AvvvA
51+
val path2 = minPath(path1) // v<<A>>^A<A>A<AAv>A^A<vAAA^>A
52+
val path3 = minPath(path2) // <vA<AA>>^AvAA<^A>Av<<A>>^AvA^Av<<A>>^AA<vA>A^A<A>Av<<A>A^>AAA<Av>A^A
53+
val num = line.init.toLong // 29
54+
val len = path3.length() // 68
55+
len * num // 211930
56+
.sum
57+
58+
@main def part1: Unit =
59+
println(s"The solution is ${part1(loadInput())}")
60+
61+
62+
/**********/
63+
/* Part 2 */
64+
/**********/
65+
66+
val cache = collection.mutable.Map.empty[(Pos, Pos, Int, Int), Long]
67+
def minPathStepCost(from: Pos, to: Pos, level: Int, maxLevel: Int): Long =
68+
cache.getOrElseUpdate((from, to, level, maxLevel), {
69+
val positions = if level == 0 then numericalKeypadPositions else directionalKeypadPositions
70+
val shift = to - from
71+
val h = (if shift.x > 0 then ">" else "<") * shift.x.abs
72+
val v = (if shift.y > 0 then "v" else "^") * shift.y.abs
73+
val reverse = !positions(from + shift.projX) || (positions(from + shift.projY) && shift.x > 0)
74+
val res = if reverse then v + h + 'A' else h + v + 'A'
75+
if level == maxLevel then res.length() else minPathCost(res, level + 1, maxLevel)
76+
})
77+
78+
def minPathCost(input: String, level: Int, maxLevel: Int): Long =
79+
val keypad = if level == 0 then numericalKeypad else directionalKeypad
80+
(s"A$input").map(keypad).sliding(2).map(p => minPathStepCost(p(0), p(1), level, maxLevel)).sum
81+
82+
def part2(input: String): Long =
83+
input
84+
.linesIterator
85+
.filter(_.nonEmpty)
86+
.map(line => minPathCost(line, 0, 25) * line.init.toLong)
87+
.sum
88+
89+
@main def part2: Unit =
90+
println(s"The solution is ${part2(loadInput())}")

0 commit comments

Comments
 (0)