From 26cbc2f3154c7db4cfc81c138ccc5e624a0306a8 Mon Sep 17 00:00:00 2001 From: pareronia <49491686+pareronia@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:48:55 +0100 Subject: [PATCH] AoC 2024 Day 4 - rust --- README.md | 2 +- src/main/rust/AoC2024_04/Cargo.toml | 7 ++ src/main/rust/AoC2024_04/src/main.rs | 100 +++++++++++++++++++++++++++ src/main/rust/Cargo.lock | 7 ++ src/main/rust/aoc/src/geometry.rs | 24 +++++++ src/main/rust/aoc/src/grid.rs | 95 +++++++++++++++++++++++++ src/main/rust/aoc/src/navigation.rs | 1 + 7 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/main/rust/AoC2024_04/Cargo.toml create mode 100644 src/main/rust/AoC2024_04/src/main.rs diff --git a/README.md b/README.md index e5a948e6..26214d8d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | python3 | [✓](src/main/python/AoC2024_01.py) | [✓](src/main/python/AoC2024_02.py) | [✓](src/main/python/AoC2024_03.py) | [✓](src/main/python/AoC2024_04.py) | | | | | | | | | | | | | | | | | | | | | | | java | [✓](src/main/java/AoC2024_01.java) | [✓](src/main/java/AoC2024_02.java) | [✓](src/main/java/AoC2024_03.java) | [✓](src/main/java/AoC2024_04.java) | | | | | | | | | | | | | | | | | | | | | | -| rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | | | | | | | | | | | | | | | | | | | | | | | +| rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | [✓](src/main/rust/AoC2024_04/src/main.rs) | | | | | | | | | | | | | | | | | | | | | | ## 2023 diff --git a/src/main/rust/AoC2024_04/Cargo.toml b/src/main/rust/AoC2024_04/Cargo.toml new file mode 100644 index 00000000..92252566 --- /dev/null +++ b/src/main/rust/AoC2024_04/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "AoC2024_04" +version = "0.1.0" +edition = "2021" + +[dependencies] +aoc = { path = "../aoc" } diff --git a/src/main/rust/AoC2024_04/src/main.rs b/src/main/rust/AoC2024_04/src/main.rs new file mode 100644 index 00000000..22324ebe --- /dev/null +++ b/src/main/rust/AoC2024_04/src/main.rs @@ -0,0 +1,100 @@ +#![allow(non_snake_case)] + +use aoc::geometry::Direction; +use aoc::grid::{CharGrid, Grid}; +use aoc::Puzzle; +use std::collections::HashSet; + +struct AoC2024_04; + +impl AoC2024_04 {} + +impl aoc::Puzzle for AoC2024_04 { + type Input = CharGrid; + type Output1 = usize; + type Output2 = usize; + + aoc::puzzle_year_day!(2024, 4); + + fn parse_input(&self, lines: Vec) -> Self::Input { + CharGrid::from(&lines.iter().map(AsRef::as_ref).collect::>()) + } + + fn part_1(&self, grid: &Self::Input) -> Self::Output1 { + grid.cells() + .filter(|cell| grid.get(cell) == 'X') + .map(|cell| { + Direction::octants() + .iter() + .filter(|dir| { + let mut it = grid.cells_direction(&cell, **dir); + ['M', 'A', 'S'].iter().all(|ch| match it.next() { + Some(n) => grid.get(&n) == *ch, + None => false, + }) + }) + .count() + }) + .sum() + } + + fn part_2(&self, grid: &Self::Input) -> Self::Output2 { + let directions = [ + Direction::LeftAndUp, + Direction::RightAndDown, + Direction::RightAndUp, + Direction::LeftAndDown, + ]; + let matches = HashSet::from(["MSMS", "SMSM", "MSSM", "SMMS"]); + grid.cells() + .filter(|cell| { + 0 < cell.row + && cell.row < grid.height() - 1 + && 0 < cell.col + && cell.col < grid.width() - 1 + }) + .filter(|cell| grid.get(cell) == 'A') + .filter(|cell| { + let s = directions + .iter() + .map(|d| grid.get(&cell.try_at(*d).unwrap())) + .collect::(); + matches.contains(&s as &str) + }) + .count() + } + + fn samples(&self) { + aoc::puzzle_samples! { + self, part_1, TEST, 18, + self, part_2, TEST, 9 + }; + } +} + +fn main() { + AoC2024_04 {}.run(std::env::args()); +} + +const TEST: &str = "\ +MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX +"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn samples() { + AoC2024_04 {}.samples(); + } +} diff --git a/src/main/rust/Cargo.lock b/src/main/rust/Cargo.lock index a5854396..f0f61d5c 100644 --- a/src/main/rust/Cargo.lock +++ b/src/main/rust/Cargo.lock @@ -411,6 +411,13 @@ dependencies = [ "regex", ] +[[package]] +name = "AoC2024_04" +version = "0.1.0" +dependencies = [ + "aoc", +] + [[package]] name = "aho-corasick" version = "1.0.2" diff --git a/src/main/rust/aoc/src/geometry.rs b/src/main/rust/aoc/src/geometry.rs index 60eab877..a6c25b34 100644 --- a/src/main/rust/aoc/src/geometry.rs +++ b/src/main/rust/aoc/src/geometry.rs @@ -30,9 +30,13 @@ impl XY { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Direction { Up, + RightAndUp, Right, + RightAndDown, Down, + LeftAndDown, Left, + LeftAndUp, } impl Direction { @@ -47,6 +51,21 @@ impl Direction { .collect() } + pub fn octants() -> HashSet { + vec![ + Direction::Up, + Direction::RightAndUp, + Direction::Right, + Direction::RightAndDown, + Direction::Down, + Direction::LeftAndDown, + Direction::Left, + Direction::LeftAndUp, + ] + .into_iter() + .collect() + } + pub fn is_horizontal(&self) -> bool { *self == Direction::Right || *self == Direction::Left } @@ -73,6 +92,7 @@ impl Direction { Turn::Left => Direction::Down, Turn::Right => Direction::Up, }, + _ => panic!("Invalid Direction for turn: {}", self), } } } @@ -109,9 +129,13 @@ impl TryFrom for XY { fn try_from(value: Direction) -> Result { match value { Direction::Up => Ok(XY::of(0, 1)), + Direction::RightAndUp => Ok(XY::of(1, 1)), Direction::Right => Ok(XY::of(1, 0)), + Direction::RightAndDown => Ok(XY::of(1, -1)), Direction::Down => Ok(XY::of(0, -1)), + Direction::LeftAndDown => Ok(XY::of(-1, -1)), Direction::Left => Ok(XY::of(-1, 0)), + Direction::LeftAndUp => Ok(XY::of(-1, 1)), } } } diff --git a/src/main/rust/aoc/src/grid.rs b/src/main/rust/aoc/src/grid.rs index 2557f010..0a3a509c 100644 --- a/src/main/rust/aoc/src/grid.rs +++ b/src/main/rust/aoc/src/grid.rs @@ -88,9 +88,13 @@ pub struct CellRange { pub enum Direction { Forward, North, + NorthEast, East, + SouthEast, South, + SouthWest, West, + NorthWest, } #[derive(Clone, Copy, Debug)] @@ -125,6 +129,13 @@ impl Iterator for GridIterator { }; self.next } + Direction::NorthEast => { + self.next = match val.row > 0 && val.col < self.width - 1 { + true => Some(Cell::at(val.row - 1, val.col + 1)), + false => None, + }; + self.next + } Direction::East => { self.next = match val.col < self.width - 1 { true => Some(Cell::at(val.row, val.col + 1)), @@ -132,6 +143,15 @@ impl Iterator for GridIterator { }; self.next } + Direction::SouthEast => { + self.next = match val.row < self.height - 1 + && val.col < self.width - 1 + { + true => Some(Cell::at(val.row + 1, val.col + 1)), + false => None, + }; + self.next + } Direction::South => { self.next = match val.row < self.height - 1 { true => Some(Cell::at(val.row + 1, val.col)), @@ -139,6 +159,13 @@ impl Iterator for GridIterator { }; self.next } + Direction::SouthWest => { + self.next = match val.row < self.height - 1 && val.col > 0 { + true => Some(Cell::at(val.row + 1, val.col - 1)), + false => None, + }; + self.next + } Direction::West => { self.next = match val.col > 0 { true => Some(Cell::at(val.row, val.col - 1)), @@ -146,6 +173,13 @@ impl Iterator for GridIterator { }; self.next } + Direction::NorthWest => { + self.next = match val.row > 0 && val.col > 0 { + true => Some(Cell::at(val.row - 1, val.col - 1)), + false => None, + }; + self.next + } }, } } @@ -249,6 +283,31 @@ pub trait Grid { } } + fn cells_direction( + &self, + cell: &Cell, + direction: crate::geometry::Direction, + ) -> GridIterator { + match direction { + crate::geometry::Direction::Up => self.cells_north(cell), + crate::geometry::Direction::RightAndUp => { + self.cells_north_east(cell) + } + crate::geometry::Direction::Right => self.cells_east(cell), + crate::geometry::Direction::RightAndDown => { + self.cells_south_east(cell) + } + crate::geometry::Direction::Down => self.cells_south(cell), + crate::geometry::Direction::LeftAndDown => { + self.cells_south_west(cell) + } + crate::geometry::Direction::Left => self.cells_west(cell), + crate::geometry::Direction::LeftAndUp => { + self.cells_north_west(cell) + } + } + } + fn cells_north(&self, cell: &Cell) -> GridIterator { GridIterator { width: self.width(), @@ -258,6 +317,15 @@ pub trait Grid { } } + fn cells_north_east(&self, cell: &Cell) -> GridIterator { + GridIterator { + width: self.width(), + height: self.height(), + direction: Direction::NorthEast, + next: Some(Cell::at(cell.row, cell.col)), + } + } + fn cells_east(&self, cell: &Cell) -> GridIterator { GridIterator { width: self.width(), @@ -267,6 +335,15 @@ pub trait Grid { } } + fn cells_south_east(&self, cell: &Cell) -> GridIterator { + GridIterator { + width: self.width(), + height: self.height(), + direction: Direction::SouthEast, + next: Some(Cell::at(cell.row, cell.col)), + } + } + fn cells_south(&self, cell: &Cell) -> GridIterator { GridIterator { width: self.width(), @@ -276,6 +353,15 @@ pub trait Grid { } } + fn cells_south_west(&self, cell: &Cell) -> GridIterator { + GridIterator { + width: self.width(), + height: self.height(), + direction: Direction::SouthWest, + next: Some(Cell::at(cell.row, cell.col)), + } + } + fn cells_west(&self, cell: &Cell) -> GridIterator { GridIterator { width: self.width(), @@ -285,6 +371,15 @@ pub trait Grid { } } + fn cells_north_west(&self, cell: &Cell) -> GridIterator { + GridIterator { + width: self.width(), + height: self.height(), + direction: Direction::NorthWest, + next: Some(Cell::at(cell.row, cell.col)), + } + } + // TODO return iterator fn cells_capital_directions(&self, cell: &Cell) -> Vec { vec![ diff --git a/src/main/rust/aoc/src/navigation.rs b/src/main/rust/aoc/src/navigation.rs index cb66097b..1c61d201 100644 --- a/src/main/rust/aoc/src/navigation.rs +++ b/src/main/rust/aoc/src/navigation.rs @@ -26,6 +26,7 @@ impl From for Heading { Direction::Right => Heading::East, Direction::Down => Heading::South, Direction::Left => Heading::West, + _ => panic!("Direction not supported: {}", direction), } } }