From 79408a37e8aea90b757b58224cd7b3a181882ce3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Dec 2024 15:16:16 +0100 Subject: [PATCH] feat: add day 4 --- README.md | 3 +- data/examples/04.txt | 10 +++ src/bin/04.rs | 181 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 data/examples/04.txt create mode 100644 src/bin/04.rs diff --git a/README.md b/README.md index 12b2d6a..6ac87b5 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 1](./src/bin/01.rs) | `72.0µs` | `76.8µs` | | [Day 2](./src/bin/02.rs) | `215.6µs` | `371.6µs` | | [Day 3](./src/bin/03.rs) | `652.5µs` | `736.7µs` | +| [Day 4](./src/bin/04.rs) | `5.6ms` | `2.4ms` | -**Total: 2.13ms** +**Total: 10.13ms** --- diff --git a/data/examples/04.txt b/data/examples/04.txt new file mode 100644 index 0000000..1f4eda2 --- /dev/null +++ b/data/examples/04.txt @@ -0,0 +1,10 @@ +MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX diff --git a/src/bin/04.rs b/src/bin/04.rs new file mode 100644 index 0000000..e0921bc --- /dev/null +++ b/src/bin/04.rs @@ -0,0 +1,181 @@ +advent_of_code::solution!(4); + +#[derive(Debug, Clone)] +struct Pos { + x: usize, + y: usize, +} + +impl Pos { + fn init(x: usize, y: usize) -> Self { + Self { x, y } + } + fn get_pos_from_origin(&self) -> Vec { + (0..self.y) + .flat_map(|y| (0..self.x).map(|x| Pos::init(x, y)).collect::>()) + .collect::>() + } + fn diag_pos(&self, max: &Pos) -> Option> { + if self.y == 0 || self.y >= max.y - 1 || self.x == 0 || self.x >= max.x - 1 { + None + } else { + Some(vec![ + // Northwest + Pos::init(self.x - 1, self.y - 1), + // Northeast + Pos::init(self.x + 1, self.y - 1), + // Southwest + Pos::init(self.x - 1, self.y + 1), + // Southeast + Pos::init(self.x + 1, self.y + 1), + ]) + } + } + + fn xmas_pos(&self, max: &Pos) -> Vec> { + let mut res: Vec> = Vec::new(); + if self.y >= 3 { + if self.x >= 3 { + // Northwest + res.push( + (1..4) + .map(|z| Pos::init(self.x - z as usize, self.y - z as usize)) + .collect(), + ); + } + // North + res.push( + (1..4) + .map(|y| Pos::init(self.x, self.y - y as usize)) + .collect(), + ); + if self.x < max.x - 3 { + // Northeast + res.push( + (1..4) + .map(|z| Pos::init(self.x + z as usize, self.y - z as usize)) + .collect(), + ); + } + } + if self.x >= 3 { + // West + res.push( + (1..4) + .map(|x| Pos::init(self.x - x as usize, self.y)) + .collect(), + ); + } + if self.x < max.x - 3 { + // East + res.push( + (1..4) + .map(|x| Pos::init(self.x + x as usize, self.y)) + .collect(), + ); + } + if self.y < max.y - 3 { + if self.x >= 3 { + // Southwest + res.push( + (1..4) + .map(|z| Pos::init(self.x - z as usize, self.y + z as usize)) + .collect(), + ); + } + // South + res.push( + (1..4) + .map(|y| Pos::init(self.x, self.y + y as usize)) + .collect(), + ); + if self.x < max.x - 3 { + // Southeast + res.push( + (1..4) + .map(|z| Pos::init(self.x + z as usize, self.y + z as usize)) + .collect(), + ); + } + } + // println!("Surrounding pos for {:?}", self); + // println!("{:#?}", res); + res + } + fn get_char_in_input(&self, input: &[&str]) -> char { + input[self.y].chars().nth(self.x).unwrap_or('0') + } +} + +fn mas_at_pos(input: &[&str], ps: &[Pos]) -> bool { + for (p, l) in ps.iter().zip("MAS".chars()) { + let c = p.get_char_in_input(input); + if c != l { + return false; + } + } + true +} + +fn cross_mas_at_pos(input: &[&str], ps: &[Pos]) -> bool { + let letters = ps + .iter() + .map(|p| p.get_char_in_input(input)) + .collect::>(); + letters.iter().all(|&c| c == 'M' || c == 'S') + && letters[0] != letters[3] + && letters[1] != letters[2] +} + +pub fn part_one(input: &str) -> Option { + let input = input + .strip_suffix("\n") + .unwrap_or(input) + .lines() + .collect::>(); + let input = input.as_slice(); + let max = Pos::init(input[0].len(), input.len()); + max.get_pos_from_origin() + .iter() + .filter(|p| p.get_char_in_input(input) == 'X') + .flat_map(|p| p.xmas_pos(&max)) + .filter(|ps| mas_at_pos(input, ps)) + .collect::>() + .len() + .into() +} + +pub fn part_two(input: &str) -> Option { + let input = input + .strip_suffix("\n") + .unwrap_or(input) + .lines() + .collect::>(); + let input = input.as_slice(); + let max = Pos::init(input[0].len(), input.len()); + max.get_pos_from_origin() + .iter() + .filter(|p| p.get_char_in_input(input) == 'A') + .flat_map(|p| p.diag_pos(&max)) + .filter(|ps| cross_mas_at_pos(input, ps)) + .collect::>() + .len() + .into() +} + +#[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(18)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(9)); + } +}