главная Π½ΠΎΠ²ΠΎΠ΅ Π»ΡƒΡ‡ΡˆΠ΅Π΅ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ

Π—Π°Π΄Π°Ρ‡Π° 6-Π³ΠΎ дня Advent of Code β€” Guard Gallivant.

πŸ“˜ Π§Π°ΡΡ‚ΡŒ I. ОписаниС Π·Π°Π΄Π°Ρ‡ΠΈ

Нам трСбуСтся ΡΠΌΠΎΠ΄Π΅Π»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ° ΠΏΠΎ Π»Π°Π±ΠΎΡ€Π°Ρ‚ΠΎΡ€ΠΈΠΈ, прСдставлСнной Π² Π²ΠΈΠ΄Π΅ сСтки.

Π—Π°Π΄Π°Ρ‡Π° β€” ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ количСство ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹Ρ… ΠΊΠ»Π΅Ρ‚ΠΎΠΊ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊ посСтит, ΠΏΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ ΠΏΠΎΠΊΠΈΠ½Π΅Ρ‚ ΠΊΠ°Ρ€Ρ‚Ρƒ.​

πŸ’‘ ИдСя

НСобходимо ΡΠΈΠΌΡƒΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ°, учитывая Π΅Π³ΠΎ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ ΠΈ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅. Для отслСТивания посСщённых ΠΊΠ»Π΅Ρ‚ΠΎΠΊ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π΄Π²ΡƒΠΌΠ΅Ρ€Π½Ρ‹ΠΉ массив, Π³Π΄Π΅ каТдая ΠΊΠ»Π΅Ρ‚ΠΊΠ° Ρ…Ρ€Π°Π½ΠΈΡ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ направлСниях, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΠΎΠ½Π° Π±Ρ‹Π»Π° посСщСна. Если ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊ возвращаСтся Π² Ρ‚Ρƒ ΠΆΠ΅ ΠΊΠ»Π΅Ρ‚ΠΊΡƒ с Ρ‚Π΅ΠΌ ΠΆΠ΅ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ, это ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚ Π½Π°Ρ‡Π°Π»ΠΎ Ρ†ΠΈΠΊΠ»Π°.

πŸ“˜ Π§Π°ΡΡ‚ΡŒ II. ОписаниС Π·Π°Π΄Π°Ρ‡ΠΈ

Π’ΠΎ Π²Ρ‚ΠΎΡ€ΠΎΠΉ части Π·Π°Π΄Π°Ρ‡ΠΈ трСбуСтся ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ количСство ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΉ, Π³Π΄Π΅ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Π½ΠΎΠ²ΠΎΠ³ΠΎ прСпятствия ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Ρ‚ ΠΊ Ρ‚ΠΎΠΌΡƒ, Ρ‡Ρ‚ΠΎ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊ зациклится ΠΈ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΠΏΠΎΠΊΠΈΠ½Π΅Ρ‚ ΠΊΠ°Ρ€Ρ‚Ρƒ. ΠžΡ‡Π΅Π²ΠΈΠ΄Π½ΠΎ, Ρ‡Ρ‚ΠΎ нСльзя Ρ€Π°Π·ΠΌΠ΅Ρ‰Π°Ρ‚ΡŒ прСпятствиС Π² Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ°.​

πŸ’‘ ИдСя

Для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠ»Π΅Ρ‚ΠΊΠΈ, посСщённой ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠΎΠΌ Π² ΠΏΠ΅Ρ€Π²ΠΎΠΉ части (ΠΊΡ€ΠΎΠΌΠ΅ Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠΉ), Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ добавляСм прСпятствиС ΠΈ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎ симулируСм Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ°. Если ΠΏΡ€ΠΈ этом ΠΎΠ½ ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ‚ Π² Ρ†ΠΈΠΊΠ», ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ счётчик. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, опрСдСляСм количСство ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΉ, Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ прСпятствия Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ Π·Π°Ρ†ΠΈΠΊΠ»ΠΈΠ²Π°Π½ΠΈΠ΅.

πŸ” Π”Π΅Ρ‚Π°Π»ΠΈ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ

  1. Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Direction: прСдставляСт Ρ‡Π΅Ρ‚Ρ‹Ρ€Π΅ направлСния двиТСния ΠΈ содСрТит ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для ΠΏΠΎΠ²ΠΎΡ€ΠΎΡ‚Π° Π½Π°ΠΏΡ€Π°Π²ΠΎ, опрСдСлСния ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΉ ΠΊΠ»Π΅Ρ‚ΠΊΠΈ ΠΈ получСния Π±ΠΈΡ‚ΠΎΠ²ΠΎΠΉ маски направлСния.​
  2. Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Guard: Ρ…Ρ€Π°Π½ΠΈΡ‚ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ ΠΈ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ°. ΠœΠ΅Ρ‚ΠΎΠ΄ step Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅Ρ‚ Π»ΠΎΠ³ΠΈΠΊΡƒ двиТСния: Ссли ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠΎΠΌ прСпятствиС ΠΈΠ»ΠΈ Π³Ρ€Π°Π½ΠΈΡ†Π°, ΠΎΠ½ ΠΏΠΎΠ²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π½Π°ΠΏΡ€Π°Π²ΠΎ; ΠΈΠ½Π°Ρ‡Π΅ Π΄Π΅Π»Π°Π΅Ρ‚ шаг Π²ΠΏΠ΅Ρ€Ρ‘Π΄.​
  3. ΠœΠ΅Ρ‚ΠΎΠ΄ simulate_guard: симулируСт Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ° Π΄ΠΎ Π²Ρ‹Ρ…ΠΎΠ΄Π° Π·Π° Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ ΠΊΠ°Ρ€Ρ‚Ρ‹ ΠΈΠ»ΠΈ Π΄ΠΎ попадания Π² Ρ†ΠΈΠΊΠ». Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Ρ„Π»Π°Π³ Π²Ρ‹Ρ…ΠΎΠ΄Π° ΠΈ массив посСщённых Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠ»Π΅Ρ‚ΠΊΠΈ.​
  4. ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ solve_simple ΠΈ solve_hard: Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΡŽΡ‚ Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ для ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΈ Π²Ρ‚ΠΎΡ€ΠΎΠΉ части Π·Π°Π΄Π°Ρ‡ΠΈ соотвСтствСнно, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΡΠΈΠΌΡƒΠ»ΡΡ†ΠΈΡŽ двиТСния ΠΎΡ…Ρ€Π°Π½Π½ΠΈΠΊΠ°, ΠΈ анализируя Π΅Π³ΠΎ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹.​

πŸ“ˆ Асимптотика

🧾 Π˜ΡΡ…ΠΎΠ΄Π½Ρ‹ΠΉ ΠΊΠΎΠ΄

use super::TaskData;

/// Represents the four cardinal directions the guard can face.
#[derive(Debug, Clone, Copy)]
pub enum Direction {
    Up,
    Right,
    Down,
    Left,
}

/// Represents the type of each cell in the grid.
#[derive(Debug, Clone, Copy)]
pub enum CellType {
    Free,
    Obstacle,
}

/// Encapsulates the guard's position and facing direction.
#[derive(Debug, Clone, Copy)]
pub struct Guard {
    pub row: usize,
    pub col: usize,
    pub dir: Direction,
}

/// Solution module containing methods to solve both parts of the problem.
pub struct Solution;

impl Solution {
    /// Solves Part I: Calculates the number of distinct positions the guard visits
    /// before leaving the mapped area.
    pub fn solve_simple(data: &TaskData) -> i32 {
        let (moved_out, seen_dirs) =
            Self::simulate_guard(data.guard.clone(), &data.field, (data.rows, data.cols));
        if !moved_out {
            panic!("Guard is stuck in a loop.");
        }
        Self::count_seen_cells(&seen_dirs) as i32
    }

    /// Solves Part II: Determines the number of positions where placing a new obstacle
    /// would cause the guard to get stuck in a loop.
    pub fn solve_hard(data: &TaskData) -> i32 {
        let mut field = data.field.clone();
        let (_, seen_dirs) =
            Self::simulate_guard(data.guard.clone(), &field, (data.rows, data.cols));

        (0..data.rows)
            .flat_map(|r| (0..data.cols).map(move |c| (r, c)))
            .filter(|&(r, c)| (r, c) != (data.guard.row, data.guard.col) && seen_dirs[r][c] != 0)
            .filter_map(|(r, c)| {
                field[r][c] = CellType::Obstacle;
                let (moved_out, _) = 
                    Self::simulate_guard(data.guard.clone(), &field, (data.rows, data.cols));
                field[r][c] = CellType::Free;

                (!moved_out).then_some((r,c))
            }).count() as i32
    }

    /// Simulates the guard's movement until they either leave the grid or enter a loop.
    fn simulate_guard(
        mut guard: Guard,
        field: &[Vec<CellType>],
        limits: (usize, usize),
    ) -> (bool, Vec<Vec<i32>>) {
        let (rows, cols) = limits;
        let mut seen_dirs = vec![vec![0; cols]; rows];

        while seen_dirs[guard.row][guard.col] & guard.dir.bitmask() == 0 {
            // Mark the current cell as visited from the current direction.
            seen_dirs[guard.row][guard.col] |= guard.dir.bitmask();

            if !Self::step_guard(&mut guard, field, limits) {
                return (true, seen_dirs); // Guard has moved out of bounds.
            }
        }

        (false, seen_dirs) // Guard is stuck in a loop.
    }

    /// Attempts to move the guard forward. If an obstacle is ahead, the guard turns right.
    fn step_guard(guard: &mut Guard, field: &[Vec<CellType>], limits: (usize, usize)) -> bool {
        match guard.dir.next_cell(guard.row, guard.col, limits) {
            Some((nr, nc)) => {
                if matches!(field[nr][nc], CellType::Obstacle) {
                    guard.dir = guard.dir.turn_right();
                } else {
                    guard.row = nr;
                    guard.col = nc;
                }
                true
            }
            None => false, // Guard would move out of bounds
        }
    }

    /// Counts the number of distinct cells that the guard has visited.
    fn count_seen_cells(seen_dirs: &[Vec<i32>]) -> usize {
        seen_dirs.iter()
            .flat_map(|row| row.iter())
            .filter(|&&v| v != 0)
            .count()
    }
 }

impl Direction {
    /// Returns the direction obtained by turning right from the current direction.
    fn turn_right(self) -> Self {
        match self {
            Self::Up => Self::Right,
            Self::Right => Self::Down,
            Self::Down => Self::Left,
            Self::Left => Self::Up,
        }
    }

    /// Calculates the next cell position based on the current direction.
    fn next_cell(self, row: usize, col: usize, limits: (usize, usize)) -> Option<(usize, usize)> {
        match self {
            Self::Up if row > 0 => Some((row - 1, col)),
            Self::Right if col + 1 < limits.1 => Some((row, col + 1)),
            Self::Down if row + 1 < limits.0 => Some((row + 1, col)),
            Self::Left if col > 0 => Some((row, col - 1)),
            _ => None,
        }
    }

    /// Returns a bitmask corresponding to the current direction.
    fn bitmask(self) -> i32 {
        match self {
            Self::Up => 1,
            Self::Right => 2,
            Self::Down => 4,
            Self::Left => 8,
        }
    }
}

Tags: #rust #algorithms #adventofcode #simulation