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