Skip to content

Commit 1d7fa8e

Browse files
committed
2024/13 turdo polisho
1 parent 1597930 commit 1d7fa8e

File tree

1 file changed

+60
-21
lines changed

1 file changed

+60
-21
lines changed

src/main/scala/y2024/Day13.scala

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,88 @@ package y2024
33
// see https://adventofcode.com/2024/day/13
44
class Day13 extends util.Day(13):
55

6-
def solvePart1(input: IndexedSeq[String]): Any =
6+
def solvePart1(input: IndexedSeq[String]): Long =
77
val machines = parseMachines(input)
88
val solutions = machines.flatMap(_.solveMachine)
99
solutions.sum
1010

11-
def solvePart2(input: IndexedSeq[String]): Any =
11+
def solvePart2(input: IndexedSeq[String]): Long =
1212
val offset = 10_000_000_000_000L
1313
val machines = parseMachines(input).map(_.offset(offset))
1414
val solutions = machines.flatMap(_.solveMachine)
1515
solutions.sum
1616

1717
private def parseMachines(input: IndexedSeq[String]): IndexedSeq[Machine] =
1818
input
19-
.filterNot(_.trim.isEmpty)
19+
.filterNot(_.isEmpty)
2020
.grouped(3)
2121
.map: lines =>
2222
val (xa, ya) = parseLine(lines(0))
2323
val (xb, yb) = parseLine(lines(1))
2424
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)
2626
.toIndexedSeq
27+
end parseMachines
2728

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
3435
(xv, yv)
36+
end parseLine
3537

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)
3940

4041
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
4469
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
5081

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
5190
end Day13

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy