Solved day 13 part 1, I can't figure out part 2

This commit is contained in:
2024-12-14 01:44:22 +01:00
parent b1f53dfd72
commit 7ea8174698
4 changed files with 1456 additions and 2 deletions

1279
input/day13.txt Normal file

File diff suppressed because it is too large Load Diff

15
input/day13_example.txt Normal file
View File

@@ -0,0 +1,15 @@
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176
Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450
Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279

157
src/day13.rs Normal file
View File

@@ -0,0 +1,157 @@
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>
}
#[derive(Copy, Clone, Debug)]
struct ClawMachine {
button_a: Coord<u64>,
button_b: Coord<u64>,
prize: Coord<u64>
}
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;
}
}
0
}
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
}
}
impl Day13 {
pub fn create(input: String) -> Self {
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();
ClawMachine {
button_a, button_b, prize
}
})
.collect()
}
}
}
impl DaySolver for Day13 {
fn solve_part1(&mut self) -> String {
self.machines.iter()
.filter_map(|m| m.find_cheapest_win())
.sum::<u64>().to_string()
}
fn solve_part2(&mut self) -> String {
self.machines.iter()
.map(|m| ClawMachine {
button_a: m.button_a,
button_b: m.button_b,
prize: m.prize.add(&Coord::new(10000000000000u64, 10000000000000u64))
})
.filter_map(|m| m.find_cheapest_win())
.sum::<u64>().to_string()
}
}
#[test]
fn test_part1() {
let mut day = Day13::create(read_file("input/day13_example.txt"));
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());
}

View File

@@ -1,10 +1,10 @@
extern crate core;
use std::time::Instant;
use crate::day1::Day1;
use crate::day10::Day10;
use crate::day11::Day11;
use crate::day12::Day12;
use crate::day1::Day1;
use crate::day13::Day13;
use crate::day2::Day2;
use crate::day3::Day3;
use crate::day4::Day4;
@@ -15,6 +15,7 @@ use crate::day8::Day8;
use crate::day9::Day9;
use crate::day_solver::DaySolver;
use crate::util::read_file;
use std::time::Instant;
mod util;
mod day_solver;
@@ -30,6 +31,7 @@ mod day9;
mod day10;
mod day11;
mod day12;
mod day13;
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
@@ -113,6 +115,7 @@ fn build_day_solver(day: u8, input: String) -> Option<Box<dyn DaySolver>> {
10 => Some(Box::new(Day10::create(input))),
11 => Some(Box::new(Day11::create(input))),
12 => Some(Box::new(Day12::create(input))),
13 => Some(Box::new(Day13::create(input))),
_ => None
}
}