@@ -3,49 +3,88 @@ package y2024
3
3
// see https://adventofcode.com/2024/day/13
4
4
class Day13 extends util.Day (13 ):
5
5
6
- def solvePart1 (input : IndexedSeq [String ]): Any =
6
+ def solvePart1 (input : IndexedSeq [String ]): Long =
7
7
val machines = parseMachines(input)
8
8
val solutions = machines.flatMap(_.solveMachine)
9
9
solutions.sum
10
10
11
- def solvePart2 (input : IndexedSeq [String ]): Any =
11
+ def solvePart2 (input : IndexedSeq [String ]): Long =
12
12
val offset = 10_000_000_000_000L
13
13
val machines = parseMachines(input).map(_.offset(offset))
14
14
val solutions = machines.flatMap(_.solveMachine)
15
15
solutions.sum
16
16
17
17
private def parseMachines (input : IndexedSeq [String ]): IndexedSeq [Machine ] =
18
18
input
19
- .filterNot(_.trim. isEmpty)
19
+ .filterNot(_.isEmpty)
20
20
.grouped(3 )
21
21
.map: lines =>
22
22
val (xa, ya) = parseLine(lines(0 ))
23
23
val (xb, yb) = parseLine(lines(1 ))
24
24
val (x, y) = parseLine(lines(2 ))
25
- Machine (xa.toInt , ya.toInt , xb.toInt , yb.toInt , x, y)
25
+ Machine (xa, ya, xb, yb, x, y)
26
26
.toIndexedSeq
27
+ end parseMachines
27
28
28
- private def parseLine (line : String ): (Long , Long ) =
29
- val parts = line.split(" ," ).map(_.trim)
30
- val xPart = parts(0 ).split(" X" )(1 ).trim
31
- val xv = xPart.replace(" =" , " " ).toLong
32
- val yPart = parts(1 ).split(" Y" )(1 ).trim
33
- val yv = yPart.replace(" =" , " " ).toLong
29
+ private def parseLine (line : String ): (Int , Int ) =
30
+ val parts = line.split(" ," )
31
+ val xPart = parts(0 ).split(" X" )(1 )
32
+ val xv = xPart.replace(" =" , " " ).toInt
33
+ val yPart = parts(1 ).split(" Y" )(1 )
34
+ val yv = yPart.replace(" =" , " " ).toInt
34
35
(xv, yv)
36
+ end parseLine
35
37
36
-
37
- case class Machine (xa : Int , ya : Int , xb : Int , yb : Int , x : Long , y : Long ):
38
- def offset (offset : Long ): Machine = copy(x = x + offset, y = y + offset)
38
+ case class Machine (velocityX1 : Int , velocityY1 : Int , velocityX2 : Int , velocityY2 : Int , targetX : Long , targetY : Long ):
39
+ def offset (offset : Long ): Machine = copy(targetX = targetX + offset, targetY = targetY + offset)
39
40
40
41
def solveMachine : Option [Long ] =
41
- // great discussion: https://www.reddit.com/r/adventofcode/comments/1hd7irq/2024_day_13_an_explanation_of_the_mathematics/
42
- val det = xb.toLong * ya.toLong - yb.toLong * xa.toLong
43
- if det == 0 then None
42
+ /*
43
+
44
+ We need to solve:
45
+ timeA * velocityX1 + timeB * velocityX2 = targetX (equation 1)
46
+ timeA * velocityY1 + timeB * velocityY2 = targetY (equation 2)
47
+
48
+ In matrix form this is:
49
+ | velocityX1 velocityX2 | | timeA | = | targetX |
50
+ | velocityY1 velocityY2 | | timeB | | targetY |
51
+
52
+ The determinant = (velocityX2 * velocityY1 - velocityY2 * velocityX1) tells us:
53
+ - If det = 0: The matrix is singular (no unique solution exists)
54
+ This means the button movements are parallel/linearly dependent,
55
+ and we can never reach the target
56
+ - If det ≠ 0: We can solve for timeA and timeB using Cramer's rule:
57
+ timeA = (velocityX2 * targetY - velocityY2 * targetX) / determinant
58
+ timeB = (targetX * velocityY1 - targetY * velocityX1) / determinant
59
+
60
+ The numerators in these calculations represent distances in the coordinate system.
61
+ We then check if these distances are evenly divisible by our movement rates
62
+ to ensure we can reach the target with whole number button presses.
63
+
64
+ */
65
+ val determinant = velocityX2 * velocityY1 - velocityY2 * velocityX1
66
+
67
+ if determinant == 0 then
68
+ None // Matrix is singular - buttons movements are parallel/dependent
44
69
else
45
- val aNum = xb.toLong * y - yb.toLong * x
46
- val bNum = x - (aNum / det) * xa
47
- if aNum % det != 0 then None
48
- else if bNum % xb.toLong != 0 then None
49
- else Some (3 * aNum / det + bNum / xb)
70
+ // Using Cramer's rule to solve for timeA first
71
+ val distanceA = velocityX2 * targetY - velocityY2 * targetX
72
+
73
+ // Check if this distance is evenly divisible by our movement rate
74
+ if distanceA % determinant != 0 then
75
+ None // No integer solution for number of A button presses
76
+ else
77
+ val timeA = distanceA / determinant
78
+
79
+ // Calculate remaining distance to target after A moves
80
+ val remainingDistance = targetX - timeA * velocityX1
50
81
82
+ // Check if remaining distance is evenly divisible by B's movement rate
83
+ if remainingDistance % velocityX2 != 0 then
84
+ None // No integer solution for number of B button presses
85
+ else
86
+ val timeB = remainingDistance / velocityX2
87
+ // Cost is 3 tokens per A press plus 1 token per B press
88
+ Some (3 * timeA + timeB)
89
+ end solveMachine
51
90
end Day13
0 commit comments