From 992756faa6604099257aab8f04fcba45910cb6a4 Mon Sep 17 00:00:00 2001 From: uku Date: Wed, 4 Dec 2024 21:57:40 +0100 Subject: [PATCH] feat(day4): init --- src/solutions/day_04.rs | 127 ++++++++++++++++++++++++++++++++++++++++ src/solutions/mod.rs | 8 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/solutions/day_04.rs diff --git a/src/solutions/day_04.rs b/src/solutions/day_04.rs new file mode 100644 index 0000000..37cd359 --- /dev/null +++ b/src/solutions/day_04.rs @@ -0,0 +1,127 @@ +use itertools::Itertools; + +use crate::common::{Answer, Solution}; + +pub struct Day04; + +impl Solution for Day04 { + fn name(&self) -> &'static str { + "Ceres Search" + } + + fn part_a(&self, input: &str) -> Answer { + let horizontal = input.lines().map(xmas_count).sum::(); + let vertical = rotate_90(input).map(xmas_count).sum::(); + let diag_45 = rotate_45(input).map(xmas_count).sum::(); + let diag_135 = rotate_45(&rotate_90(input).join("\n")) + .map(xmas_count) + .sum::(); + + let sum = horizontal + vertical + diag_45 + diag_135; + + Answer::Number(sum as u64) + } + + fn part_b(&self, input: &str) -> Answer { + let input_90 = rotate_90(input).join("\n"); + let input_180 = rotate_90(&input_90).join("\n"); + let input_270 = rotate_90(&input_180).join("\n"); + + let sum = cross_mas_count(input) + + cross_mas_count(&input_90) + + cross_mas_count(&input_180) + + cross_mas_count(&input_270); + + Answer::Number(sum as u64) + } +} + +fn rotate_90(input: &str) -> impl Iterator + use<'_> { + let col_count = input.lines().next().unwrap().chars().count(); + + (0..col_count) + .rev() // this is important otherwise 135 rotation doesn't work + .map(|i| input.lines().map(|l| l.chars().nth(i).unwrap()).join("")) +} + +fn rotate_45(input: &str) -> impl Iterator + use<'_> { + let line_count = input.lines().count(); + let col_count = input.lines().next().unwrap().chars().count(); + + let cols = (0..col_count).map(|i| traverse_diagonal(input, (0, i))); + let lines = (1..line_count).map(|i| traverse_diagonal(input, (i, 0))); + + cols.chain(lines) +} + +fn traverse_diagonal(input: &str, start: (usize, usize)) -> String { + (start.0..) + .zip(start.1..) + .map_while(|(x, y)| input.lines().nth(x).and_then(|l| l.chars().nth(y))) + .collect() +} + +fn xmas_count(line: impl AsRef) -> usize { + line.as_ref().matches("XMAS").count() + line.as_ref().matches("SAMX").count() +} + +fn cross_mas_count(input: &str) -> usize { + let mut sum = 0; + + for (x, line) in input.lines().enumerate() { + for (y, _) in line.match_indices('A') { + let prev_line = x.checked_sub(1).and_then(|n| input.lines().nth(n)); + let next_line = input.lines().nth(x + 1); + + if let (Some(prev_line), Some(next_line)) = (prev_line, next_line) { + let prev_y = y.checked_sub(1); + let next_y = y + 1; + + let prev_prev = prev_y.and_then(|n| prev_line.chars().nth(n)); + let prev_next = prev_line.chars().nth(next_y); + + let next_prev = prev_y.and_then(|n| next_line.chars().nth(n)); + let next_next = next_line.chars().nth(next_y); + + if let (Some('M'), Some('M'), Some('S'), Some('S')) = + (prev_prev, prev_next, next_prev, next_next) + { + sum += 1; + } + } + } + } + + sum +} + +#[cfg(test)] +mod test { + use super::Day04; + use crate::common::Solution; + + use indoc::indoc; + + const INPUT: &str = indoc! {" + MMMSXXMASM + MSAMXMSMSA + AMXSXMAAMM + MSAMASMSMX + XMASAMXAMM + XXAMMXXAMA + SMSMSASXSS + SAXAMASAAA + MAMMMXMMMM + MXMXAXMASX + "}; + + #[test] + fn part_a() { + assert_eq!(Day04.part_a(INPUT), 18.into()); + } + + #[test] + fn part_b() { + assert_eq!(Day04.part_b(INPUT), 9.into()); + } +} diff --git a/src/solutions/mod.rs b/src/solutions/mod.rs index f8c8bdb..e27f88f 100644 --- a/src/solutions/mod.rs +++ b/src/solutions/mod.rs @@ -3,5 +3,11 @@ use crate::common::Solution; mod day_01; mod day_02; mod day_03; +mod day_04; -pub const SOLUTIONS: &[&dyn Solution] = &[&day_01::Day01, &day_02::Day02, &day_03::Day03]; +pub const SOLUTIONS: &[&dyn Solution] = &[ + &day_01::Day01, + &day_02::Day02, + &day_03::Day03, + &day_04::Day04, +];