Files
advent-of-code-2024-rust/src/day14.rs
2024-12-16 00:47:06 +01:00

155 lines
4.6 KiB
Rust

use crate::day_solver::DaySolver;
#[cfg(test)]
use crate::util::read_file;
use crate::util::{Coord, Grid};
pub struct Day14 {
robots: Vec<Robot>
}
#[derive(Debug, Copy, Clone)]
struct Robot {
p: Coord<i32>,
v: Coord<i32>
}
impl Robot {
fn step(&mut self, w: i32, h: i32) {
self.p = self.p.add(&self.v);
if self.p.x >= w { self.p.x -= w; }
if self.p.x < 0 { self.p.x += w; }
if self.p.y >= h { self.p.y -= h; }
if self.p.y < 0 { self.p.y += h; }
}
}
impl Day14 {
pub fn create(input: String) -> Self {
return Day14 {
robots: input.lines()
.filter_map(|l| l[2..].split_once(" v="))
.map(|(p, v)| Robot {
p: p.split_once(",").map(|(x, y)| Coord::new(x.parse().unwrap(), y.parse().unwrap())).unwrap(),
v: v.split_once(",").map(|(x, y)| Coord::new(x.parse().unwrap(), y.parse().unwrap())).unwrap()
})
.collect()
}
}
#[allow(dead_code)]
fn print(&self, robots: &Vec<Robot>) {
let (w, h) = self.map_size();
let mut grid = Grid {
data: vec![0; (w*h).try_into().unwrap()],
width: w.try_into().unwrap(),
default: None
};
for r in robots {
let x = r.p.x.try_into().unwrap();
let y = r.p.y.try_into().unwrap();
grid.set(x, y, grid.get(x, y) + 1);
}
let print_grid = Grid {
data: grid.data.iter().map(|g| if g > &0 { '#' } else { ' ' } ).collect(),
width: grid.width,
default: None
};
print_grid.print();
}
fn map_size(&self) -> (i32, i32) {
// First we have to know if we're in the example case or the real case, which we guess by checking if any robots are outside a 11 by 7 area.
if self.robots.iter().any(|r| r.p.x > 11 || r.p.y > 7) {
(101, 103)
} else {
(11, 7)
}
}
fn variance(data: &[i32]) -> f64 {
let sum = data.iter().sum::<i32>();
let n = f64::from(data.len() as u32);
let mean = f64::from(sum) / n;
(data.iter().map(|x| (f64::from(*x) - mean).powi(2)).sum::<f64>() / (n - 1f64)).sqrt()
}
fn variance_f(data: &[f64]) -> f64 {
let sum = data.iter().sum::<f64>();
let n = f64::from(data.len() as u32);
let mean = sum / n;
(data.iter().map(|n| (f64::from(*n) - mean).powi(2)).sum::<f64>() / (n - 1f64)).sqrt()
}
}
impl DaySolver for Day14 {
fn solve_part1(&mut self) -> String {
let (w, h) = self.map_size();
let mut robots = self.robots.clone();
for _ in 0..100 {
for robot in &mut robots {
robot.step(w, h);
}
}
(robots.iter().filter(|r| r.p.x < w / 2 && r.p.y < h / 2).count() *
robots.iter().filter(|r| r.p.x > w / 2 && r.p.y < h / 2).count() *
robots.iter().filter(|r| r.p.x < w / 2 && r.p.y > h / 2).count() *
robots.iter().filter(|r| r.p.x > w / 2 && r.p.y > h / 2).count()).to_string()
}
fn solve_part2(&mut self) -> String {
let (w, h) = self.map_size();
let mut robots = self.robots.clone();
let mut x_variances = Vec::new();
let mut y_variances = Vec::new();
// We're gonna simulate the robots, and try to find anomalies in the variance in x and y positions.
for i in 1..10000 {
for robot in &mut robots {
robot.step(w, h);
}
let var_x = Self::variance(&robots.iter().map(|r| r.p.x).collect::<Vec<i32>>());
let var_y = Self::variance(&robots.iter().map(|r| r.p.y).collect::<Vec<i32>>());
// To find anomalies, we find the variance in the variance of X, and if the variance in this sample is more than 3 times the variance of the variance
// away from usual, we found the outlier!
let var_var_x = Self::variance_f(&x_variances);
let var_var_y = Self::variance_f(&y_variances);
x_variances.push(var_x);
y_variances.push(var_y);
// We assume
// println!("var: {}, {}", var_x, var_y);
if i > 1 && (var_x < x_variances[0] - 3f64 * var_var_x && var_y < y_variances[0] - 3f64 * var_var_y) {
// self.print(&robots);
return i.to_string();
}
}
panic!("Couldn't find easter egg");
}
}
#[test]
fn test_part1() {
let mut day = Day14::create(read_file("input/day14_example.txt"));
assert_eq!("12", day.solve_part1());
}