Day 13
This commit is contained in:
parent
499c2ab588
commit
9784fa357e
1 changed files with 159 additions and 0 deletions
159
src/bin/day13.rs
Normal file
159
src/bin/day13.rs
Normal 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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue