This commit is contained in:
Quentin Boyer 2024-12-15 12:09:42 +01:00
parent 9784fa357e
commit aa7d522ef7

171
src/bin/day14.rs Normal file
View file

@ -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<Self, Self::Err> {
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<Robot>;
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
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(())
}