From 9adda430e589e9e3502d672e9a687b46ecdb8a72 Mon Sep 17 00:00:00 2001 From: Jack Kinsey Date: Wed, 4 Dec 2024 21:29:39 -0500 Subject: [PATCH] Complete day 4 --- src/day04.rs | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 6 +- 2 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/day04.rs diff --git a/src/day04.rs b/src/day04.rs new file mode 100644 index 0000000..96e8f7b --- /dev/null +++ b/src/day04.rs @@ -0,0 +1,260 @@ +fn input() -> &'static str { + include_str!("../input/day04.txt") +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +enum Char { + X, + M, + A, + S, +} + +fn parse(input: &str) -> Vec> { + input + .split_terminator('\n') + .map(|v| { + v.split("") + .filter(|s| !s.is_empty()) + .map(|c| match c { + "X" => Char::X, + "M" => Char::M, + "A" => Char::A, + "S" => Char::S, + _ => panic!(), + }) + .collect() + }) + .collect() +} + +fn find_chars(chars: &[Vec], char: Char) -> Vec<(i32, i32)> { + chars + .iter() + .enumerate() + .flat_map(|(i, v)| { + v.iter() + .enumerate() + .filter(|(_, &c)| c == char) + .map(move |(j, _)| (i, j)) + .map(|(i, j)| (i.try_into().unwrap(), j.try_into().unwrap())) + }) + .collect() +} + +#[rustfmt::skip] +const DIRS: [(i32, i32); 8] = [ + (-1, -1), (-1, 0), (-1, 1), + (0, -1), /* ctr */ (0, 1), + (1, -1), (1, 0), (1, 1), +]; + +fn make_dirs((i, j): (i32, i32), n: i32) -> Vec<[(usize, usize); 4]> { + DIRS.iter() + .map(|dir| { + [dir] + .iter() + .cycle() + .take(4) + .enumerate() + .map(|(k, dij)| (std::convert::TryInto::::try_into(k).unwrap(), dij)) + .map(|(k, (di, dj))| (i + k * di, j + k * dj)) + .collect::>() + .try_into() + .unwrap() + }) + .filter(|ds: &[(i32, i32); 4]| { + ds.iter() + .all(|(ii, jj)| *ii >= 0 && *jj >= 0 && *ii < n && *jj < n) + }) + .map(|ds: [(i32, i32); 4]| { + ds.iter() + .map(|(ii, jj)| { + ( + TryInto::::try_into(*ii).unwrap(), + TryInto::::try_into(*jj).unwrap(), + ) + }) + .collect::>() + .try_into() + .unwrap() + }) + .collect() +} + +#[rustfmt::skip] +const XDIRS: [(i32, i32); 4] = [ + (-1, -1), (-1, 1), + /* ctr */ + (1, -1), (1, 1), +]; + +fn make_xdirs((i, j): (i32, i32), n: i32) -> Option<[(usize, usize); 4]> { + if i == 0 || i == n - 1 || j == 0 || j == n - 1 { + None + } else { + Some( + XDIRS + .iter() + .map(|&(di, dj)| (i + di, j + dj)) + .map(|(ii, jj)| { + ( + TryInto::::try_into(ii).unwrap(), + TryInto::::try_into(jj).unwrap(), + ) + }) + .collect::>() + .try_into() + .unwrap(), + ) + } +} + +fn check_x_for_xmas(chars: &[Vec], coords: (i32, i32)) -> usize { + make_dirs(coords, chars.len().try_into().unwrap()) + .iter() + .map(|ijs| { + matches!( + ijs.iter().map(|(i, j)| chars[*i][*j]).collect::>()[..], + [Char::X, Char::M, Char::A, Char::S] + ) + }) + .filter(|&x| x) + .count() +} + +fn check_xs_for_xmas(chars: &[Vec]) -> usize { + find_chars(chars, Char::X) + .iter() + .map(|&coords| check_x_for_xmas(chars, coords)) + .sum() +} + +const XPATTERNS: [[Char; 4]; 4] = [ + [Char::M, Char::M, Char::S, Char::S], + [Char::M, Char::S, Char::M, Char::S], + [Char::S, Char::S, Char::M, Char::M], + [Char::S, Char::M, Char::S, Char::M], +]; + +fn check_a_for_xmas(chars: &[Vec], coords: (i32, i32)) -> usize { + if let Some(x) = make_xdirs(coords, chars.len().try_into().unwrap()) { + let xc: [Char; 4] = x + .iter() + .map(|(i, j): &(usize, usize)| chars[*i][*j]) + .collect::>() + .try_into() + .unwrap(); + XPATTERNS.iter().filter(|&&p| p == xc).count() + } else { + 0 + } +} + +fn check_as_for_xmas(chars: &[Vec]) -> usize { + find_chars(chars, Char::A) + .iter() + .map(|&coords| check_a_for_xmas(chars, coords)) + .sum() +} + +pub fn part1() { + let n = check_xs_for_xmas(&parse(input())); + println!("Day 4 Part 1: {}", n); +} + +pub fn part2() { + let n = check_as_for_xmas(&parse(input())); + println!("Day 4 Part 2: {}", n); +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT_STR: &str = concat!( + "MMMSXXMASM\n", + "MSAMXMSMSA\n", + "AMXSXMAAMM\n", + "MSAMASMSMX\n", + "XMASAMXAMM\n", + "XXAMMXXAMA\n", + "SMSMSASXSS\n", + "SAXAMASAAA\n", + "MAMMMXMMMM\n", + "MXMXAXMASX\n", + ); + + #[rustfmt::skip] + #[test] + fn test_parse() { + assert_eq!( + parse(INPUT_STR), + [ + [ Char::M, Char::M, Char::M, Char::S, Char::X, Char::X, Char::M, Char::A, Char::S, Char::M ], + [ Char::M, Char::S, Char::A, Char::M, Char::X, Char::M, Char::S, Char::M, Char::S, Char::A ], + [ Char::A, Char::M, Char::X, Char::S, Char::X, Char::M, Char::A, Char::A, Char::M, Char::M ], + [ Char::M, Char::S, Char::A, Char::M, Char::A, Char::S, Char::M, Char::S, Char::M, Char::X ], + [ Char::X, Char::M, Char::A, Char::S, Char::A, Char::M, Char::X, Char::A, Char::M, Char::M ], + [ Char::X, Char::X, Char::A, Char::M, Char::M, Char::X, Char::X, Char::A, Char::M, Char::A ], + [ Char::S, Char::M, Char::S, Char::M, Char::S, Char::A, Char::S, Char::X, Char::S, Char::S ], + [ Char::S, Char::A, Char::X, Char::A, Char::M, Char::A, Char::S, Char::A, Char::A, Char::A ], + [ Char::M, Char::A, Char::M, Char::M, Char::M, Char::X, Char::M, Char::M, Char::M, Char::M ], + [ Char::M, Char::X, Char::M, Char::X, Char::A, Char::X, Char::M, Char::A, Char::S, Char::X ] + ] + ) + } + + #[rustfmt::skip] + #[test] + fn test_find_xs() { + assert_eq!( + find_chars(&parse(INPUT_STR), Char::X), + [ + (0, 4), (0, 5), + (1, 4), + (2, 2), (2, 4), + (3, 9), + (4, 0), (4, 6), + (5, 0), (5, 1), (5, 5), (5, 6), + (6, 7), + (7, 2), + (8, 5), + (9, 1), (9, 3), (9, 5), (9, 9) + ] + ) + } + + #[test] + fn test_make_dirs() { + assert_eq!( + make_dirs((0, 4), 10), + [ + [(0, 4), (0, 3), (0, 2), (0, 1)], + [(0, 4), (0, 5), (0, 6), (0, 7)], + [(0, 4), (1, 3), (2, 2), (3, 1)], + [(0, 4), (1, 4), (2, 4), (3, 4)], + [(0, 4), (1, 5), (2, 6), (3, 7)] + ] + ) + } + + #[test] + fn test_check_xs() { + assert_eq!(check_xs_for_xmas(&parse(INPUT_STR)), 18) + } + + #[test] + fn test_make_xdirs() { + assert_eq!(make_xdirs((0, 4), 10), None); + assert_eq!( + make_xdirs((1, 1), 10), + Some([(0, 0), (0, 2), (2, 0), (2, 2)]) + ); + } + + #[test] + fn test_check_as() { + assert_eq!(check_as_for_xmas(&parse(INPUT_STR)), 9) + } +} diff --git a/src/main.rs b/src/main.rs index d6680b5..ca144b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ pub mod day01; pub mod day02; pub mod day03; -// pub mod day04; +pub mod day04; // pub mod day05; // pub mod day06; // pub mod day07; @@ -26,11 +26,11 @@ pub mod day03; type Part = fn(); -const DAYS: [(Part, Part); 3] = [ +const DAYS: [(Part, Part); 4] = [ (day01::part1 as fn(), day01::part2 as fn()), (day02::part1 as fn(), day02::part2 as fn()), (day03::part1 as fn(), day03::part2 as fn()), - // (day04::part1 as fn(), day04::part2 as fn()), + (day04::part1 as fn(), day04::part2 as fn()), // (day05::part1 as fn(), day05::part2 as fn()), // (day06::part1 as fn(), day06::part2 as fn()), // (day07::part1 as fn(), day07::part2 as fn()), -- 2.38.5