From aa7d522ef7ff4ad5f6e29d05e8b3f6485e63536b Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Sun, 15 Dec 2024 12:09:42 +0100 Subject: [PATCH] Day 14 --- src/bin/day14.rs | 171 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/bin/day14.rs diff --git a/src/bin/day14.rs b/src/bin/day14.rs new file mode 100644 index 0000000..bf31251 --- /dev/null +++ b/src/bin/day14.rs @@ -0,0 +1,171 @@ +use std::{collections::HashSet, str::FromStr, time::Instant}; + +use aoc_2024::{load, print_res}; +use bstr::BString; +use color_eyre::eyre::{bail, Context}; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct Vec2 { + x: i64, + y: i64, +} + +impl FromStr for Vec2 { + type Err = color_eyre::Report; + + fn from_str(s: &str) -> Result { + let Some((x, y)) = s.split_once(',') else { + bail!("Malformed vec2: `{s}`") + }; + + Ok(Self { + x: x.parse().with_context(|| format!("Malformed x: {x}"))?, + y: y.parse().with_context(|| format!("Malformed y: {y}"))?, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct Robot { + pos: Vec2, + vel: Vec2, +} + +type Parsed = Vec; + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + std::str::from_utf8(input)? + .lines() + .map(|r| { + let Some((p, v)) = r.split_once(' ') else { + bail!("Malformed line `{r}`") + }; + + let Some(p) = p.strip_prefix("p=") else { + bail!("Malformed position `{p}`"); + }; + + let Some(v) = v.strip_prefix("v=") else { + bail!("Malformed position `{v}`"); + }; + + Ok(Robot { + pos: p.parse().with_context(|| "Could not parse position")?, + vel: v.parse().with_context(|| "Could not parse velocity")?, + }) + }) + .collect() +} + +// const PART_1_WIDTH: i64 = 11; +const PART_1_WIDTH: i64 = 101; + +// const PART_1_HEIGHT: i64 = 7; +const PART_1_HEIGHT: i64 = 103; + +#[allow(unused)] +fn display(robots: &[Robot]) { + for y in 0..PART_1_HEIGHT { + for x in 0..PART_1_WIDTH { + let count = robots + .iter() + .filter(|r| r.pos.x == x && r.pos.y == y) + .count(); + + let char = match count { + 0 => b'.', + 1..=9 => b'0' + (count as u8), + _ => b'*', + }; + print!("{}", char as char) + } + println!() + } +} + +fn robot_quadrants(robots: &[Robot]) -> [[usize; 2]; 2] { + const { + assert!(PART_1_HEIGHT % 2 == 1); + assert!(PART_1_WIDTH % 2 == 1); + } + + let mut quadrants = [[0, 0], [0, 0]]; + + let x_mid = PART_1_WIDTH / 2; + let y_mid = PART_1_HEIGHT / 2; + + for robot in robots { + if robot.pos.x == x_mid || robot.pos.y == y_mid { + continue; + } + + quadrants[(robot.pos.y < y_mid) as usize][(robot.pos.x < x_mid) as usize] += 1; + } + + quadrants +} + +#[inline(never)] +pub fn part1(mut input: Parsed) { + for robot in &mut input { + robot.pos.x += robot.vel.x * 100; + robot.pos.x = robot.pos.x.rem_euclid(PART_1_WIDTH); + + robot.pos.y += robot.vel.y * 100; + robot.pos.y = robot.pos.y.rem_euclid(PART_1_HEIGHT); + } + + let quadrants = robot_quadrants(&input); + let safety_factor: usize = quadrants.iter().flatten().product(); + print_res!("Safety factor: {safety_factor}"); +} + +#[inline(never)] +pub fn part2(mut input: Parsed) { + assert!(!input + .iter() + .any(|x| x.vel.x % PART_1_WIDTH == 0 || x.vel.y % PART_1_HEIGHT == 0)); + + let average = input.len() / 4; + let cuttoff = 10; + + // We are guaranteed to loop after this, as both are prime + for index in 0..PART_1_HEIGHT * PART_1_WIDTH { + for robot in &mut input { + robot.pos.x = (robot.pos.x + robot.vel.x).rem_euclid(PART_1_WIDTH); + robot.pos.y = (robot.pos.y + robot.vel.y).rem_euclid(PART_1_HEIGHT); + } + + let quadrants = robot_quadrants(&input); + if quadrants + .as_flattened() + .iter() + .all(|&x| x.abs_diff(average) > cuttoff) + { + println!("==== {} ====", index + 1); + display(&input); + } + } +} + +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(()) +}