Solved day 13 part 2 as well, it was much easier than I though

This commit is contained in:
2024-12-14 01:58:47 +01:00
parent 7ea8174698
commit 86cc88ce69

View File

@@ -2,7 +2,6 @@ use crate::day_solver::DaySolver;
#[cfg(test)]
use crate::util::read_file;
use crate::util::Coord;
use num::Integer;
pub struct Day13 {
machines: Vec<ClawMachine>
@@ -10,86 +9,26 @@ pub struct Day13 {
#[derive(Copy, Clone, Debug)]
struct ClawMachine {
button_a: Coord<u64>,
button_b: Coord<u64>,
prize: Coord<u64>
button_a: Coord<i64>,
button_b: Coord<i64>,
prize: Coord<i64>
}
impl ClawMachine {
fn find_base(p: u64, a: u64, b: u64) -> u64 {
for i in 0..(p / a) + 1 {
let a_x = a * i;
if (p - a_x) % b == 0 {
return i;
}
fn find_cheapest_win(&self) -> Option<i64> {
// Turns out, this is a system of linear equations, and if there is an integer solution, it works out.
// For a system with 2 equations and two parameters like this, we can just use a standard solution:
//Determinant = (a₁b₂ - a₂b₁)
let determinant = self.button_a.x * self.button_b.y - self.button_a.y * self.button_b.x;
if (self.prize.x * self.button_b.y - self.prize.y * self.button_b.x) % determinant != 0 || (self.button_a.x * self.prize.y - self.button_a.y * self.prize.x) % determinant != 0 {
return None
}
0
}
let button_a_presses = (self.prize.x * self.button_b.y - self.prize.y * self.button_b.x) / determinant;
let button_b_presses = (self.button_a.x * self.prize.y - self.button_a.y * self.prize.x) / determinant;
fn find_cheapest_win(&self) -> Option<u64> {
// We brute force, it's probably possible to math but am lazy
// Ok, I guess we need to math
// a*x + b*y = c
// with a, b and c constants
// Googling this, we find "the linear Diophantine equation". Which has integer solutions if C is divisible by the greatest common divisor of A & B:
let (gcd_x, lcm_x) = self.button_a.x.gcd_lcm(&self.button_b.x);
let (gcd_y, lcm_y) = self.button_a.y.gcd_lcm(&self.button_b.y);
if self.prize.x % gcd_x == 0 {
// There are solutions for x!
if self.prize.y % gcd_y == 0 {
// A solution exists! We just need to find the cheapest one
// Some experimentation showed that all integer solutions for x0 + b.x / gcd_x gives possible solutions so that X works out
// I suppose the same works for y, as in: y0 + b.y / gcd_y
let x_step = self.button_b.x / gcd_x;
let y_step = self.button_b.y / gcd_y;
let x0 = Self::find_base(self.prize.x, self.button_a.x, self.button_b.x);
let y0 = Self::find_base(self.prize.y, self.button_a.y, self.button_b.y);
// So now we just need to solve:
// x0 + k_a*x_step = y0 + k_b*y_step
// x0 + k_a*x_step = y0 + ((p_x - k_a) / b_x)*y_step
// x0 - y0 = (p_x - k) * y_step / b_x + k*x_step
//
// Solving for k:
// (x0 - y0) / k = (b.y/gcd_y) - (b.x/gcd_x)
// k = (y0 - x0) / (s_x - s_y)
// When do they even hit?
// x0 + x * x_step = y0 + y * y_step
// (x * a + x0 - y0) / b = y
// Can you express y in terms of x? because we know the final target (the prize)?
// x0 + x * x_step = y0 + y * y_step
// Ok as a matrix:
// [ a_x a_y | p_x ]
// [ b_x b_y | p_y ]
println!("{:?} has gcd x={}, y={}, and lcm x={}, y={}, x0={}, y0={}, x_step={}, y_step={}", self, gcd_x, gcd_y, lcm_x, lcm_y, x0, y0, x_step, y_step);
let mut min_cost = None;
for i in (x0..(self.prize.x / self.button_a.x) + 1).step_by(x_step.try_into().unwrap()) {
let a_x = self.button_a.x * i;
if a_x > self.prize.x { break; }
let b_button_presses = (self.prize.x - a_x) / self.button_b.x;
println!("Found solution for X, pressing A button {} times, B button {} times", i, b_button_presses);
// self.prize.y == self.button_a.y * i + self.button_b.y * (self.prize.x - self.button_a.x * i) / self.button_b.x
// p_y = b_y * i + b_y * (p_x - a_x * i) / b_x
if self.prize.y == self.button_a.y * i + self.button_b.y * (self.prize.x - self.button_a.x * i) / self.button_b.x {
let cost = i * 3 + b_button_presses;
println!("Found solution a button {}, b button {}", i, b_button_presses);
if min_cost.is_none() || min_cost.unwrap() > cost {
min_cost = Some(cost)
}
}
}
return min_cost
}
}
None
// println!("button A={}, button B={}, det={}", button_a_presses, button_b_presses, determinant);
Some(button_a_presses * 3 + button_b_presses)
}
}
@@ -101,9 +40,9 @@ impl Day13 {
machines: input.split("\n\n")
.map(|c| {
let mut c_lines = c.split("\n");
let button_a = c_lines.next().unwrap()["Button A: X+".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<u64>().unwrap(), y[2..].parse::<u64>().unwrap())).unwrap();
let button_b = c_lines.next().unwrap()["Button B: X+".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<u64>().unwrap(), y[2..].parse::<u64>().unwrap())).unwrap();
let prize = c_lines.next().unwrap()["Prize: X=".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<u64>().unwrap(), y[2..].parse::<u64>().unwrap())).unwrap();
let button_a = c_lines.next().unwrap()["Button A: X+".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<i64>().unwrap(), y[2..].parse::<i64>().unwrap())).unwrap();
let button_b = c_lines.next().unwrap()["Button B: X+".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<i64>().unwrap(), y[2..].parse::<i64>().unwrap())).unwrap();
let prize = c_lines.next().unwrap()["Prize: X=".len()..].split_once(", ").map(|(x, y)| Coord::new(x.parse::<i64>().unwrap(), y[2..].parse::<i64>().unwrap())).unwrap();
ClawMachine {
button_a, button_b, prize
}
@@ -119,7 +58,7 @@ impl DaySolver for Day13 {
fn solve_part1(&mut self) -> String {
self.machines.iter()
.filter_map(|m| m.find_cheapest_win())
.sum::<u64>().to_string()
.sum::<i64>().to_string()
}
fn solve_part2(&mut self) -> String {
@@ -127,10 +66,10 @@ impl DaySolver for Day13 {
.map(|m| ClawMachine {
button_a: m.button_a,
button_b: m.button_b,
prize: m.prize.add(&Coord::new(10000000000000u64, 10000000000000u64))
prize: m.prize.add(&Coord::new(10000000000000, 10000000000000))
})
.filter_map(|m| m.find_cheapest_win())
.sum::<u64>().to_string()
.sum::<i64>().to_string()
}
}
@@ -140,18 +79,8 @@ fn test_part1() {
assert_eq!("480", day.solve_part1());
}
#[test]
fn test_machine() {
let machine = ClawMachine {
button_a: Coord::new(94, 34),
button_b: Coord::new(22, 67),
prize: Coord::new(8400*2, 5400*4)
};
assert_eq!(Some(12), machine.find_cheapest_win());
}
#[test]
fn test_part2() {
let mut day = Day13::create(read_file("input/day13_example.txt"));
assert_eq!("EXAMPLE_ANSWER", day.solve_part2());
assert_eq!("875318608908", day.solve_part2());
}