From 352cb38e8abc8be0c69267d66112af8ba466b66f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Sun, 15 Dec 2024 22:15:36 +0100 Subject: [PATCH] feat: add day 14 --- README.md | 6 +- data/examples/14.txt | 12 +++ src/bin/14.rs | 214 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 data/examples/14.txt create mode 100644 src/bin/14.rs diff --git a/README.md b/README.md index 4bdfe43..26d4a1a 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,12 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 8](./src/bin/08.rs) | `110.0µs` | `395.4µs` | | [Day 9](./src/bin/09.rs) | `897.8µs` | `549.0ms` | | [Day 10](./src/bin/10.rs) | `836.4µs` | `1.6ms` | +| [Day 11](./src/bin/11.rs) | `829.0ns` | `1.0µs` | +| [Day 12](./src/bin/12.rs) | `332.4ms` | `318.8ms` | +| [Day 13](./src/bin/13.rs) | `133.3µs` | `92.0µs` | +| [Day 14](./src/bin/14.rs) | `210.1µs` | `105.4ms` | -**Total: 12226.66ms** +**Total: 12983.70ms** --- diff --git a/data/examples/14.txt b/data/examples/14.txt new file mode 100644 index 0000000..2455da4 --- /dev/null +++ b/data/examples/14.txt @@ -0,0 +1,12 @@ +p=0,4 v=3,-3 +p=6,3 v=-1,-3 +p=10,3 v=-1,2 +p=2,0 v=2,-1 +p=0,0 v=1,3 +p=3,0 v=-2,-2 +p=7,6 v=-1,-3 +p=3,0 v=-1,-2 +p=9,3 v=2,3 +p=7,3 v=-1,2 +p=2,4 v=2,-3 +p=9,5 v=-3,-3 diff --git a/src/bin/14.rs b/src/bin/14.rs new file mode 100644 index 0000000..41a78bd --- /dev/null +++ b/src/bin/14.rs @@ -0,0 +1,214 @@ +advent_of_code::solution!(14); + +use std::collections::HashMap; +use std::iter::successors; + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Pos { + x: u16, + y: u16, +} + +impl Pos { + fn add_with_wrap(&self, v: &Velocity, wrap: &Pos) -> Self { + Self { + x: (((wrap.x as i16) + (self.x as i16) + (v.x as i16)) as u16) % wrap.x, + y: (((wrap.y as i16) + (self.y as i16) + (v.y as i16)) as u16) % wrap.y, + } + } +} + +impl TryFrom<&&str> for Pos { + type Error = (); + + fn try_from(input: &&str) -> Result { + let coords: Vec = input[2..] + .split(",") + .map(|s| s.parse::().map_err(|_| ())) + .collect::>()?; + let coords = coords.as_slice(); + match coords { + [x, y] => Ok(Self { x: *x, y: *y }), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone)] +struct Velocity { + x: i8, + y: i8, +} + +impl TryFrom<&&str> for Velocity { + type Error = (); + + fn try_from(input: &&str) -> Result { + let coords: Vec = input[2..] + .split(",") + .map(|s| s.parse::().map_err(|_| ())) + .collect::>()?; + let coords = coords.as_slice(); + match coords { + [x, y] => Ok(Self { x: *x, y: *y }), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone)] +struct Bot { + p: Pos, + v: Velocity, +} + +impl Bot { + fn next(&self, wrap: &Pos) -> Self { + Self { + v: self.v.clone(), + p: self.p.add_with_wrap(&self.v, wrap), + } + } +} + +impl TryFrom<&str> for Bot { + type Error = (); + fn try_from(input: &str) -> Result { + let parts = input.split_whitespace().collect::>(); + let parts = parts.as_slice(); + match parts { + [p, v] => Ok(Self { + p: Pos::try_from(p).map_err(|_| ())?, + v: Velocity::try_from(v).map_err(|_| ())?, + }), + _ => Err(()), + } + } +} + +struct State { + bots: Vec, + size: Pos, +} + +impl TryFrom<&str> for State { + type Error = (); + fn try_from(input: &str) -> Result { + Ok(Self { + bots: input + .lines() + .map(Bot::try_from) + .collect::, ()>>()?, + size: Pos { x: 101, y: 103 }, + }) + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + for y in 0..self.size.y { + for x in 0..self.size.x { + let p = Pos { x, y }; + if self.bots.iter().any(|b| b.p == p) { + write!(f, "*")?; + } else { + write!(f, " ")?; + } + } + writeln!(f)?; + } + + Ok(()) + } +} + +impl State { + fn anomaly(&self) -> bool { + let mut ys: HashMap = HashMap::new(); + let mut xs: HashMap = HashMap::new(); + + self.bots.iter().for_each(|b| { + *ys.entry(b.p.y).or_default() += 1; + *xs.entry(b.p.x).or_default() += 1; + }); + let mut xs = xs.into_values().collect::>(); + let mut ys = ys.into_values().collect::>(); + ys.sort(); + xs.sort(); + ys[ys.len() - 1] > 30 && xs[xs.len() - 1] > 30 + } + + fn quadrants(&self) -> [u32; 4] { + self.bots + .iter() + .map(|b| b.p.clone()) + .fold([0, 0, 0, 0], |[ul, ur, ll, lr], p| match p { + Pos { x: px, y: py } if px < self.size.x / 2 && py < self.size.y / 2 => { + [ul + 1, ur, ll, lr] + } + Pos { x: px, y: py } if px < self.size.x / 2 && py > self.size.y / 2 => { + [ul, ur, ll + 1, lr] + } + Pos { x: px, y: py } if px > self.size.x / 2 && py < self.size.y / 2 => { + [ul, ur + 1, ll, lr] + } + Pos { x: px, y: py } if px > self.size.x / 2 && py > self.size.y / 2 => { + [ul, ur, ll, lr + 1] + } + _ => [ul, ur, ll, lr], + }) + } + + fn next(&self) -> Option { + Some(Self { + bots: self + .bots + .clone() + .into_iter() + .map(|b| b.next(&self.size)) + .collect(), + size: self.size.clone(), + }) + } +} + +pub fn part_one(input: &str) -> Option { + successors(State::try_from(input).ok(), |st| st.next()) + .nth(100) + .unwrap() + .quadrants() + .iter() + .product::() + .into() +} + +pub fn part_two(input: &str) -> Option { + successors(State::try_from(input).ok(), |s| s.next()) + .enumerate() + .find(|(i, st)| st.anomaly() || *i > 10000) + .map(|(i, st)| { + if st.anomaly() { + println!("{:?}", st); + Some(i) + } else { + None + } + })? +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(21)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +}