155 lines
4.6 KiB
Rust
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());
|
|
}
|