главная новое лучшее написать
3

Задача 4-го дня Advent of Code — Ceres Search.

📌 Часть I. Условие задачи

Нам дано поле из символов. Нужно найти все вхождения слова "XMAS".
Слово может быть записано:

💡 Идея

Для поиска всех таких слов:

📌 Часть II. Условие задачи

Теперь требуется найти не само слово XMAS, а X-образное расположение двух слов "MAS" вокруг символа "A".
Пример расположения:

  M . S
  . A .
  M . S

💡 Идея

🛠 Подробности реализации

  1. Все 8 направлений закодированы как массив смещений DIRECTIONS.
  2. Функция read_in_direction позволяет прочитать последовательность символов из поля, начиная с любой точки и идущую в указанном направлении.
  3. Функция matches сравнивает итератор с ожидаемым словом. Она останавливается при первом несовпадении.
  4. Решение второй части (solve_hard) просто перебирает центр "A", и проверяет обе диагонали.

⏱ Асимптотика

Пусть n × m — размеры поля.

📄 Исходный код

use super::TaskData;

pub struct Solution;

impl Solution {
    // All 8 directions: diagonals, verticals, and horizontals
    const DIRECTIONS: [(i32, i32); 8] = [
        (-1, 1), (-1, 0), (-1, -1),
        (0, -1), (1, -1), (1, 0),
        (1, 1), (0, 1),
    ];

    /// Part I — count all "XMAS" occurrences in any direction
    pub fn solve_simple(data: &TaskData) -> i32 {
        let maze = &data.maze;
        let rows = maze.len();
        let cols = maze[0].len();
        let mut count = 0;

        for r in 0..rows {
            for c in 0..cols {
                for (dr, dc) in Self::DIRECTIONS {
                    let iter = Self::read_in_direction(maze, r, c, 4, dr, dc);
                    if Self::matches(iter, b"XMAS") {
                        count += 1;
                    }
                }
            }
        }

        count
    }

    /// Part II — count "X-MAS" patterns forming an X shape centered at 'A'
    pub fn solve_hard(data: &TaskData) -> i32 {
        let maze = &data.maze;
        let rows = maze.len();
        let cols = maze[0].len();
        let mut count = 0;

        for r in 1..rows - 1 {
            for c in 1..cols - 1 {
                if maze[r][c] != b'A' {
                    continue;
                }

                // Check diagonals around center 'A'
                let mut diag1 = [maze[r - 1][c - 1], maze[r + 1][c + 1]];
                let mut diag2 = [maze[r - 1][c + 1], maze[r + 1][c - 1]];
                diag1.sort();
                diag2.sort();

                if Self::matches(diag1.into_iter(), b"MS") && Self::matches(diag2.into_iter(), b"MS") {
                    count += 1;
                }
            }
        }

        count
    }

    /// Reads a sequence of `len` characters from (r0, c0) in direction (dr, dc)
    fn read_in_direction(maze: &[Vec<u8>], r0: usize, c0: usize, len: usize, dr: i32, dc: i32) -> impl Iterator<Item = u8> + '_ {
        let rows = maze.len() as i32;
        let cols = maze[0].len() as i32;

        std::iter::once(maze[r0][c0]).chain(
            (1..len).scan((r0, c0), move |(r, c), _| {
                let nr = *r as i32 + dr;
                let nc = *c as i32 + dc;

                ((0..rows).contains(&nr) && (0..cols).contains(&nc)).then(|| {
                    *r = nr as usize;
                    *c = nc as usize;
                    maze[*r][*c]
                })
            }),
        )
    }

    /// Returns true if the iterator matches the expected byte string exactly
    fn matches<I>(mut iter: I, expected: &[u8]) -> bool
    where
        I: Iterator<Item = u8>,
    {
        expected.iter().all(|&e| iter.next() == Some(e))
    }
}

Tags: #rust #algorithms #adventofcode #strings