--- /dev/null
+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<Vec<Char>> {
+ 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: 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::<i32>::try_into(k).unwrap(), dij))
+ .map(|(k, (di, dj))| (i + k * di, j + k * dj))
+ .collect::<Vec<_>>()
+ .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::<usize>::try_into(*ii).unwrap(),
+ TryInto::<usize>::try_into(*jj).unwrap(),
+ )
+ })
+ .collect::<Vec<_>>()
+ .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::<usize>::try_into(ii).unwrap(),
+ TryInto::<usize>::try_into(jj).unwrap(),
+ )
+ })
+ .collect::<Vec<_>>()
+ .try_into()
+ .unwrap(),
+ )
+ }
+}
+
+fn check_x_for_xmas(chars: &[Vec<Char>], 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::<Vec<_>>()[..],
+ [Char::X, Char::M, Char::A, Char::S]
+ )
+ })
+ .filter(|&x| x)
+ .count()
+}
+
+fn check_xs_for_xmas(chars: &[Vec<Char>]) -> 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<Char>], 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::<Vec<_>>()
+ .try_into()
+ .unwrap();
+ XPATTERNS.iter().filter(|&&p| p == xc).count()
+ } else {
+ 0
+ }
+}
+
+fn check_as_for_xmas(chars: &[Vec<Char>]) -> 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)
+ }
+}