This commit is contained in:
Quentin Boyer 2024-12-14 18:15:15 +01:00
parent 499c2ab588
commit 9784fa357e

159
src/bin/day13.rs Normal file
View file

@ -0,0 +1,159 @@
use std::time::Instant;
use aoc_2024::{load, print_res};
use bstr::BString;
use color_eyre::eyre::{bail, Context};
#[derive(Debug)]
struct Point {
x: i64,
y: i64,
}
#[derive(Debug)]
pub struct Crane {
a: Point,
b: Point,
prize: Point,
}
type Parsed = Vec<Crane>;
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
fn parse_point(operation: char, s: &str) -> color_eyre::Result<Point> {
let Some((x, y)) = s.split_once(", ") else {
bail!("Missing separator in `{s}`")
};
let Some(x) = x.strip_prefix('X').and_then(|s| s.strip_prefix(operation)) else {
bail!("Missing X in `${x}`")
};
let Some(y) = y.strip_prefix("Y").and_then(|s| s.strip_prefix(operation)) else {
bail!("Missing Y in `${y}`")
};
Ok(Point {
x: x.parse().with_context(|| format!("Invalid X: `{x}`"))?,
y: y.parse().with_context(|| format!("Invalid Y: `{y}`"))?,
})
}
std::str::from_utf8(input)?
.split("\n\n")
.map(|desc| {
let Some((a, rest)) = desc.trim().split_once('\n') else {
bail!("Missing first linebreak")
};
let Some((b, prize)) = rest.split_once('\n') else {
bail!("Missing second linebreak")
};
let Some(a) = a.strip_prefix("Button A: ") else {
bail!("Invalid button A: `{a}`")
};
let Some(b) = b.strip_prefix("Button B: ") else {
bail!("Invalid button B: `{b}`")
};
let Some(prize) = prize.strip_prefix("Prize: ") else {
bail!("Invalid prize: `{prize}`")
};
Ok(Crane {
a: parse_point('+', a)?,
b: parse_point('+', b)?,
prize: parse_point('=', prize)?,
})
})
.collect()
}
impl Crane {
pub fn presses(&self) -> Option<(i64, i64)> {
// X = a * AX + b * BX
// Y = a * AY + b * BY
//
// M = ( AX BX ) T = ( X ) B = ( a )
// ( AY BY ) ( Y ) ( b )
//
// M * B = ( a * AX + b * BX ) = T
// ( a * AY + b * BY )
// B = M^-1 * T
let det = self.a.x * self.b.y - self.b.x * self.a.y;
if det == 0 {
todo!()
}
// M^-1 = 1/det * ( BY -BX )
// ( -AY AX )
//
// M^-1 * T = 1/det * ( BY * X - BX * Y )
// ( -AY * X + AX * Y )
let raw_a = det.signum() * (self.b.y * self.prize.x - self.b.x * self.prize.y);
let raw_b = det.signum() * (-self.a.y * self.prize.x + self.a.x * self.prize.y);
if raw_a < 0 || raw_a % det != 0 {
return None;
}
if raw_b < 0 || raw_b % det != 0 {
return None;
}
Some((raw_a / det.abs(), raw_b / det.abs()))
}
}
#[inline(never)]
pub fn part1(input: Parsed) {
let mut cost = 0;
for crane in input {
let Some((a, b)) = crane.presses() else {
continue;
};
cost += a * 3 + b;
}
print_res!("Total cost: {cost}");
}
#[inline(never)]
pub fn part2(input: Parsed) {
let mut cost = 0;
for mut crane in input {
crane.prize.x += 10000000000000;
crane.prize.y += 10000000000000;
let Some((a, b)) = crane.presses() else {
continue;
};
cost += a * 3 + b;
}
print_res!("Total cost: {cost}");
}
pub fn main() -> color_eyre::Result<()> {
let context = load()?;
let start = Instant::now();
let parsed = parsing(&context.input)?;
let elapsed = humantime::format_duration(start.elapsed());
let start = Instant::now();
if context.part == 1 {
part1(parsed);
} else {
part2(parsed);
}
let elapsed_part = humantime::format_duration(start.elapsed());
println!(" Parsing: {elapsed}");
println!(" Solving: {elapsed_part}");
Ok(())
}