diff --git a/aoc_2024/src/day13.rs b/aoc_2024/src/day13.rs new file mode 100644 index 0000000..99e8eb3 --- /dev/null +++ b/aoc_2024/src/day13.rs @@ -0,0 +1,254 @@ +use std::cmp::Ordering; + +use aoc_runner_derive::{aoc, aoc_generator}; +use nom::bytes::complete::tag; +use nom::character::complete::{multispace0, u64 as parse_u64}; +use nom::multi::many0; +use nom::IResult; + +#[derive(Debug, Clone, Copy)] +struct Case { + button_a: (u64, u64), + button_b: (u64, u64), + prize: (u64, u64), +} + +type Input = Vec; + +#[aoc_generator(day13)] +fn parse(input: &str) -> Input { + many0(parse_case)(input).unwrap().1 +} + +fn parse_case(input: &str) -> IResult<&str, Case> { + let (input, _) = tag("Button A: X+")(input)?; + let (input, button_a_x) = parse_u64(input)?; + let (input, _) = tag(", Y+")(input)?; + let (input, button_a_y) = parse_u64(input)?; + let (input, _) = tag("\nButton B: X+")(input)?; + let (input, button_b_x) = parse_u64(input)?; + let (input, _) = tag(", Y+")(input)?; + let (input, button_b_y) = parse_u64(input)?; + let (input, _) = tag("\nPrize: X=")(input)?; + let (input, prize_x) = parse_u64(input)?; + let (input, _) = tag(", Y=")(input)?; + let (input, prize_y) = parse_u64(input)?; + let (input, _) = multispace0(input)?; + + Ok(( + input, + Case { + button_a: (button_a_x, button_a_y), + button_b: (button_b_x, button_b_y), + prize: (prize_x, prize_y), + }, + )) +} + +#[aoc(day13, part1)] +fn part1(input: &Input) -> u64 { + input + .iter() + .copied() + .map(|case| { + let (vec_low, vec_high, cost_low, cost_high) = match (case.button_a.1 * case.button_b.0) + .cmp(&(case.button_a.0 * case.button_b.1)) + { + Ordering::Greater => (case.button_b, case.button_a, 1, 3), + Ordering::Less => (case.button_a, case.button_b, 3, 1), + Ordering::Equal => panic!("Parallel"), + }; + let vec_prize = case.prize; + + let vec_low_norm = ( + vec_low.0 as f64 / ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(), + vec_low.1 as f64 / ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(), + ); + + let vec_prize_proj = ( + vec_low_norm.0 + * (vec_prize.0 as f64 * vec_low_norm.0 + vec_prize.1 as f64 * vec_low_norm.1), + vec_low_norm.1 + * (vec_prize.0 as f64 * vec_low_norm.0 + vec_prize.1 as f64 * vec_low_norm.1), + ); + + let vec_prize_perp = ( + vec_prize.0 as f64 - vec_prize_proj.0, + vec_prize.1 as f64 - vec_prize_proj.1, + ); + + let len_prize_perp = + (vec_prize_perp.0 * vec_prize_perp.0 + vec_prize_perp.1 * vec_prize_perp.1).sqrt(); + + let vec_high_proj = ( + vec_low_norm.0 + * (vec_high.0 as f64 * vec_low_norm.0 + vec_high.1 as f64 * vec_low_norm.1), + vec_low_norm.1 + * (vec_high.0 as f64 * vec_low_norm.0 + vec_high.1 as f64 * vec_low_norm.1), + ); + + let vec_high_perp = ( + vec_high.0 as f64 - vec_high_proj.0, + vec_high.1 as f64 - vec_high_proj.1, + ); + + let len_high_perp = + (vec_high_perp.0 * vec_high_perp.0 + vec_high_perp.1 * vec_high_perp.1).sqrt(); + + let n_high = len_prize_perp / len_high_perp; + + let vec_low_goal = ( + vec_prize.0 as f64 - n_high * vec_high.0 as f64, + vec_prize.1 as f64 - n_high * vec_high.1 as f64, + ); + + let len_low = ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(); + + let len_low_goal = + (vec_low_goal.0 * vec_low_goal.0 + vec_low_goal.1 * vec_low_goal.1).sqrt(); + + let n_low = len_low_goal / len_low; + + let n_high = n_high.round() as u64; + let n_low = n_low.round() as u64; + + if n_high * vec_high.0 + n_low * vec_low.0 == vec_prize.0 + && n_high * vec_high.1 + n_low * vec_low.1 == vec_prize.1 + { + n_high * cost_high + n_low * cost_low + } else { + 0 + } + }) + .sum() +} + +#[aoc(day13, part2)] +fn part2(input: &Input) -> u64 { + input + .iter() + .copied() + .map(|case| { + let (vec_low, vec_high, cost_low, cost_high) = match (case.button_a.1 * case.button_b.0) + .cmp(&(case.button_a.0 * case.button_b.1)) + { + Ordering::Greater => (case.button_b, case.button_a, 1, 3), + Ordering::Less => (case.button_a, case.button_b, 3, 1), + Ordering::Equal => panic!("Parallel"), + }; + let vec_prize = ( + case.prize.0 + 10_000_000_000_000, + case.prize.1 + 10_000_000_000_000, + ); + + let vec_low_norm = ( + vec_low.0 as f64 / ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(), + vec_low.1 as f64 / ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(), + ); + + let vec_prize_proj = ( + vec_low_norm.0 + * (vec_prize.0 as f64 * vec_low_norm.0 + vec_prize.1 as f64 * vec_low_norm.1), + vec_low_norm.1 + * (vec_prize.0 as f64 * vec_low_norm.0 + vec_prize.1 as f64 * vec_low_norm.1), + ); + + let vec_prize_perp = ( + vec_prize.0 as f64 - vec_prize_proj.0, + vec_prize.1 as f64 - vec_prize_proj.1, + ); + + let len_prize_perp = + (vec_prize_perp.0 * vec_prize_perp.0 + vec_prize_perp.1 * vec_prize_perp.1).sqrt(); + + let vec_high_proj = ( + vec_low_norm.0 + * (vec_high.0 as f64 * vec_low_norm.0 + vec_high.1 as f64 * vec_low_norm.1), + vec_low_norm.1 + * (vec_high.0 as f64 * vec_low_norm.0 + vec_high.1 as f64 * vec_low_norm.1), + ); + + let vec_high_perp = ( + vec_high.0 as f64 - vec_high_proj.0, + vec_high.1 as f64 - vec_high_proj.1, + ); + + let len_high_perp = + (vec_high_perp.0 * vec_high_perp.0 + vec_high_perp.1 * vec_high_perp.1).sqrt(); + + let n_high = len_prize_perp / len_high_perp; + + let vec_low_goal = ( + vec_prize.0 as f64 - n_high * vec_high.0 as f64, + vec_prize.1 as f64 - n_high * vec_high.1 as f64, + ); + + let len_low = ((vec_low.0 * vec_low.0 + vec_low.1 * vec_low.1) as f64).sqrt(); + + let len_low_goal = + (vec_low_goal.0 * vec_low_goal.0 + vec_low_goal.1 * vec_low_goal.1).sqrt(); + + let n_low = len_low_goal / len_low; + + let n_high = n_high.round() as u64; + let n_low = n_low.round() as u64; + + if n_high * vec_high.0 + n_low * vec_low.0 == vec_prize.0 + && n_high * vec_high.1 + n_low * vec_low.1 == vec_prize.1 + { + n_high * cost_high + n_low * cost_low + } else { + 0 + } + }) + .sum() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!( + part1(&parse( + "Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400" + )), + 280 + ); + assert_eq!( + part1(&parse( + "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" + )), + 480 + ); + assert_eq!( + part1(&parse(include_str!("../input/2024/day13.txt").trim_end())), + 28753 + ); + } + + #[test] + fn part2_example() { + assert_eq!( + part2(&parse(include_str!("../input/2024/day13.txt").trim_end())), + 102718967795500 + ); + } +} diff --git a/aoc_2024/src/lib.rs b/aoc_2024/src/lib.rs index 818035b..cddd5fc 100644 --- a/aoc_2024/src/lib.rs +++ b/aoc_2024/src/lib.rs @@ -2,6 +2,7 @@ mod day1; mod day10; mod day11; mod day12; +mod day13; mod day2; mod day3; mod day4;