Day 13. Claw Contraption
While historians are spreading on a tropical island resort, we are chilling in the
resort's arcade. The claw machine awaits us, we need to win the prize.
Part One
In the first part our task is to check if we can win the prize in 100 button presses.
Also we need to minimize the number of token spent. Each button press changes the claw
position by the fixed amount of steps to the right (X
) and forward (Y
).
Reading the task description reveals that minimizing
condition
is the red herring. This is linear equation system with two variables and two equations.
Also the input doesn't contain any parallel lines, so we always have only one solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | const (
MaxPushes = 100.0
TokensA = 3
TokensB = 1
)
type Answer struct {
A, B int
Price int
Valid bool
}
func NewAnswerFromFloats(a, b float64) Answer {
if a < 0 || b < 0 {
return Answer{}
}
if a > MaxPushes || b > MaxPushes {
return Answer{}
}
if math.Mod(a, 1) != 0 || math.Mod(b, 1) != 0 {
return Answer{}
}
ansA, ansB := int(a), int(b)
price := ansA*TokensA + ansB*TokensB
return Answer{A: ansA, B: ansB, Price: price, Valid: true}
}
type EquationSystem struct {
Xa, Ya float64
Xb, Yb float64
Xpr, Ypr float64
XaInt, YaInt int
XbInt, YbInt int
XprInt, YprInt int
}
// Equations system:
// Xa * a + Xb * b = Xpr
// Ya * a + Yb * b = Ypr
// !!!!!!!!!!!!!!!!!!!!!!!
// a = (Xpr - Xb * b) / Xa
// !!!!!!!!!!!!!!!!!!!!!!!
// Ya * ((Xpr - Xb * b) / Xa) + Yb * b = Ypr
// Ya * Xpr - Ya * Xb * b + Yb * Xa * b = Ypr * Xa
// b * (Yb * Xa - Ya * Xb) = Ypr * Xa - Ya * Xpr
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// b = (Ypr * Xa - Ya * Xpr) / (Yb * Xa - Ya * Xb)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
func (eq EquationSystem) Solve() Answer {
if eq.Xa == eq.Xb && eq.Ya == eq.Yb {
panic(fmt.Sprintf("Xa=%f, Xb=%f, Ya=%f, Yb=%f", eq.Xa, eq.Xb, eq.Ya, eq.Yb))
}
b := (eq.Ypr*eq.Xa - eq.Ya*eq.Xpr) / (eq.Yb*eq.Xa - eq.Ya*eq.Xb)
a := (eq.Xpr - eq.Xb*b) / eq.Xa
if a == 0.0 || b == 0.0 {
panic(fmt.Sprintf("zero values: a=%f, b=%f", a, b))
}
return NewAnswerFromFloats(a, b)
}
type ParsedInput = []EquationSystem
...
func PartOne(inp ParsedInput) int {
defer Track(time.Now(), "PartOne")
total := 0
for _, eq := range inp {
answer := eq.Solve()
if answer.Valid {
total += answer.Price
}
}
return total
}
|
Part Two
Part two adds 10000000000000
to each coordinate of the prize position. So if you've
tried a tree search in the first part, you would consider new way to solve the task now.
Here I've used Crammer's rule
with the matrix determinant to solve the system of equations.
We can also avoid floats prescision problems
(cause button presses are integers) by checking the remainder of the determinants' divisions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 | // The Cramer's rule to solve the system of equations
// (bonus: without floating point arithmetic)
// Xa * a + Xb * b = Xpr
// Ya * a + Yb * b = Ypr
//
// Matrix A: Vec r:
// | Xa Xb | | Xpr |
// | Ya Yb | | Ypr |
//
// det(A) = Xa * Yb - Ya * Xb
// In our case det(A) == 0 -> unsolvable system
// det(A) != 0 -> may be solvable system, depending on if the roots are integer
// a = det(Aa) / det(A)
// b = det(Ab) / det(A)
func (eq EquationSystem) SolveWithDeterminants() Answer {
detA := eq.DeterminantA()
if detA == 0 {
return Answer{}
}
detAa, detAb := eq.DeterminantAa(), eq.DeterminantAb()
a, remA := divmod(detAa, detA)
b, remB := divmod(detAb, detA)
if remA != 0 || remB != 0 { // integer check
return Answer{}
}
return NewAnswer(a, b)
}
func (eq EquationSystem) DeterminantA() int {
return determinant(eq.XaInt, eq.XbInt, eq.YaInt, eq.YbInt)
}
// Aa -- change a column in matrix A with vector r
// | Xpr Xb |
// | Ypr Yb |
func (eq EquationSystem) DeterminantAa() int {
return determinant(eq.XprInt, eq.XbInt, eq.YprInt, eq.YbInt)
}
// Ab -- change b column in matrix A with vector r
// | Xa Xpr |
// | Ya Ypr |
func (eq EquationSystem) DeterminantAb() int {
return determinant(eq.XaInt, eq.XprInt, eq.YaInt, eq.YprInt)
}
// | a b |
// | c d |
func determinant(a, b, c, d int) int {
return a*d - b*c
}
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func NewAnswer(a, b int) Answer {
if a <= 0 || b <= 0 {
return Answer{}
}
price := a*TokensA + b*TokensB
return Answer{A: a, B: b, Price: price, Valid: true}
}
const clawPosExtra = 10000000000000
func PartTwo(inp ParsedInput) int {
defer Track(time.Now(), "PartTwo")
total := 0
for _, eq := range inp {
eq.XprInt, eq.YprInt = eq.XprInt+clawPosExtra, eq.YprInt+clawPosExtra
answer := eq.SolveWithDeterminants()
if answer.Valid {
total += answer.Price
}
}
return total
}
|
- analytical solution
- system of equations