diff --git a/.aoc_tiles/tiles/2024/14.png b/.aoc_tiles/tiles/2024/14.png index c3de62b..e4f0551 100644 Binary files a/.aoc_tiles/tiles/2024/14.png and b/.aoc_tiles/tiles/2024/14.png differ diff --git a/README.md b/README.md index bfd468b..983e72b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2024 - 26 ⭐ - Rust + 2024 - 28 ⭐ - Rust

@@ -41,4 +41,7 @@ + + + diff --git a/src/day14.rs b/src/day14.rs new file mode 100644 index 0000000..3e8ac93 --- /dev/null +++ b/src/day14.rs @@ -0,0 +1,171 @@ +use aoc_runner_derive::aoc; +use grid::{AsCoord2d, Coord2d, Grid}; +use regex::Regex; +use std::str::FromStr; + +struct Robot { + pos: Coord2d, + vel: Coord2d, +} + +#[derive(Debug, Eq, PartialEq)] +enum Quadrant { + NW = 0, + NE = 1, + SW = 2, + SE = 3, +} + +impl FromStr for Robot { + type Err = Box; + fn from_str(s: &str) -> Result { + let re = Regex::new(r"p=(\d+),(\d+) v=([+-]?\d+),([+-]?\d+)").unwrap(); + match re.captures(s) { + Some(c) => Ok(Self { + pos: ( + c.get(1).unwrap().as_str().parse::().unwrap(), + c.get(2).unwrap().as_str().parse().unwrap(), + ) + .to_coord(), + vel: ( + c.get(3).unwrap().as_str().parse::().unwrap(), + c.get(4).unwrap().as_str().parse().unwrap(), + ) + .to_coord(), + }), + None => panic!(), + } + } +} + +impl Robot { + fn step(&mut self, bounds: (i64, i64)) { + let mut candidate_new_pos = ((self.pos.x() + self.vel.x()), (self.pos.y() + self.vel.y())); + if candidate_new_pos.0 < 0 { + // if pos goes negative, add the upper bound + candidate_new_pos.0 += bounds.0; + } + if candidate_new_pos.1 < 0 { + candidate_new_pos.1 += bounds.1; + } + candidate_new_pos.0 %= bounds.0; + candidate_new_pos.1 %= bounds.1; + + self.pos = candidate_new_pos.to_coord(); + } + fn quad(&self, bounds: (i64, i64)) -> Option { + let splits = (bounds.0 / 2, bounds.1 / 2); + if self.pos.x() < splits.0 && self.pos.y() < splits.1 { + Some(Quadrant::NW) + } else if self.pos.x() > splits.0 && self.pos.y() < splits.1 { + Some(Quadrant::NE) + } else if self.pos.x() < splits.0 && self.pos.y() > splits.1 { + Some(Quadrant::SW) + } else if self.pos.x() > splits.0 && self.pos.y() > splits.1 { + Some(Quadrant::SE) + } else { + None + } + } +} + +#[allow(dead_code)] +fn display(robots: &Vec, bounds: (i64, i64)) { + let grid = as_grid(robots, bounds); + for row in 0..grid.height() { + for col in 0..grid.width() { + print!( + "{}", + if *grid.get(&(col, row)).unwrap() != 0 { + "█" + } else { + " " + } + ); + } + println!(" EOL"); + } +} + +fn as_grid(robots: &Vec, bounds: (i64, i64)) -> Grid { + let mut grid = Grid::with_shape(bounds.0 as usize, bounds.1 as usize, 0); + for r in robots { + grid.increment(&r.pos, 1usize); + } + grid +} + +fn parse(input: &str) -> Vec { + input.lines().map(|l| l.parse().unwrap()).collect() +} + +fn part1_impl(input: &str, width: i64, height: i64) -> u64 { + let mut robots = parse(input); + for _ in 0..100 { + for r in &mut robots { + r.step((width, height)) + } + } + let mut counts = [0; 4]; + for r in robots { + if let Some(q) = r.quad((width, height)) { + counts[q as usize] += 1 + } + } + counts.iter().product() +} + +#[aoc(day14, part1)] +pub fn part1(input: &str) -> u64 { + part1_impl(input, 101, 103) +} + +#[aoc(day14, part2)] +pub fn part2(input: &str) -> u64 { + let width = 101; + let height = 103; + let mut robots = parse(input); + for i in 1.. { + for r in &mut robots { + r.step((width, height)) + } + // collect into lines + let g = as_grid(&robots, (width, height)); + if g.data + .chunk_by(|a, b| *a != 0 && *b != 0) + .filter(|c| !c.is_empty() && c[0] != 0) + .any(|c| c.len() > width as usize / 10) + { + return i; + } + } + unreachable!() +} + +#[cfg(test)] +mod tests { + use super::*; + const EXAMPLE: &str = "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"; + + #[test] + fn part1_example() { + assert_eq!(part1_impl(EXAMPLE, 11, 7), 12); + } + + // part 2 does not converge using the test vector + // #[test] + // fn part2_example() { + // // assert_eq!(part2(EXAMPLE), 0); + // } +} diff --git a/src/lib.rs b/src/lib.rs index e77ce9c..7b521e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ use aoc_runner_derive::aoc_lib; pub mod day1; +pub mod day10; +pub mod day11; +pub mod day12; +pub mod day13; +pub mod day14; pub mod day2; pub mod day3; pub mod day4; @@ -9,9 +14,5 @@ pub mod day6; pub mod day7; pub mod day8; pub mod day9; -pub mod day10; -pub mod day11; -pub mod day12; -pub mod day13; aoc_lib! { year = 2024 } diff --git a/utils/grid/lib.rs b/utils/grid/lib.rs index f27f5ab..9e56c96 100644 --- a/utils/grid/lib.rs +++ b/utils/grid/lib.rs @@ -3,7 +3,7 @@ use std::{ io::{BufRead, Cursor}, iter::repeat, mem::swap, - ops::{Add, Sub}, + ops::{Add, AddAssign, Sub}, str::FromStr, }; @@ -51,6 +51,18 @@ impl AsCoord2d for Coord2d { } } +impl AsCoord2d for &Coord2d { + fn to_coord(self) -> Coord2d { + self.to_owned() + } + fn x(&self) -> i64 { + self.x + } + fn y(&self) -> i64 { + self.y + } +} + impl AsCoord2d for (i32, i32) { fn to_coord(self) -> Coord2d { Coord2d { @@ -178,6 +190,18 @@ impl Grid { None => None, } } + pub fn increment<'a, A, C: AsCoord2d>(&'a mut self, c: &C, i: A) -> Option<&'a T> + where + T: AddAssign, + { + match self.valid_pos(c) { + Some(pos) => { + self.data[pos] += i; + Some(&self.data[pos]) + } + None => None, + } + } pub fn row(&self, y: i64) -> Option<&[T]> { if y < self.height() as i64 { Some(&self.data[self.pos(&(0, y)) as usize..self.pos(&(self.width, y)) as usize]) @@ -300,7 +324,10 @@ FBCG"; fn from_string() { let grid = unchecked_load(); assert_eq!(grid.data, "ABCDEFGHIJKLFBCG".as_bytes()); - assert_eq!(TEST_VECTOR_S.parse::>().unwrap().data, "ABCDEFGHIJKLFBCG".as_bytes()); + assert_eq!( + TEST_VECTOR_S.parse::>().unwrap().data, + "ABCDEFGHIJKLFBCG".as_bytes() + ); } #[test]