From 9784fa357e88647573087f81e1d4cf6d37b6c6c2 Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Sat, 14 Dec 2024 18:15:15 +0100 Subject: [PATCH] Day 13 --- src/bin/day13.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/bin/day13.rs diff --git a/src/bin/day13.rs b/src/bin/day13.rs new file mode 100644 index 0000000..740c620 --- /dev/null +++ b/src/bin/day13.rs @@ -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; + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + fn parse_point(operation: char, s: &str) -> color_eyre::Result { + 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(()) +}