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

Задача второго дня Advent of Code — Red-Nosed Reports.

📘 Часть I. Описание задачи

Имеется множество отчётов, каждый из которых представляет собой последовательность целых чисел — уровней. Отчёт считается безопасным, если:

Пример безопасных отчётов:

Пример небезопасных отчётов:

💡 Идея

Необходимо пройтись по каждой последовательности и проверить, являются ли все пары элементов:

Если хотя бы одна из этих проверок не выполняется — отчёт считается небезопасным.

📘 Часть II. Описание задачи

Инженеры ввели модуль подавления проблем, который допускает одну ошибку в последовательности. То есть теперь отчёт считается безопасным, если:

💡 Идея

Для каждой последовательности мы должны проверить:

Удаление может быть:

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

  1. Мы создаём структуру MonotonicChecker, в которую инкапсулируем:
    • максимально допустимый шаг (max_step)
    • порядок сравнения (Ordering::Less или Ordering::Greater)
  2. Методы:
    • is_monotonic: итеративно проверяет, что последовательность монотонна с допустимыми шагами
    • is_monotonic_with_skip: рекурсивно проверяет, можно ли добиться монотонности, пропустив один элемент
    • is_valid_pair: Проверяет, что пара чисел следует нужному порядку (< или >) и отличается не более чем на max_step.

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

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

use super::TaskData;
use std::cmp::Ordering;

pub struct Solution;

impl Solution {
    const INC_CHECKER: MonotonicChecker = MonotonicChecker {
        max_step: 3,
        order: Ordering::Less,
    };

    const DEC_CHECKER: MonotonicChecker = MonotonicChecker {
        max_step: 3,
        order: Ordering::Greater,
    };

    /// Counts strictly safe sequences: fully increasing or decreasing,
    /// with adjacent elements differing by at most `max_step`.
    pub fn solve_simple(data: &TaskData) -> i32 {
        data.sequences
            .iter()
            .filter(|seq| {
                Self::INC_CHECKER.is_monotonic(seq)
                    || Self::DEC_CHECKER.is_monotonic(seq)
            })
            .count() as _
    }

    /// Counts sequences that are safe either directly or after skipping one element.
    pub fn solve_hard(data: &TaskData) -> i32 {
        data.sequences
            .iter()
            .filter(|seq| {
                Self::INC_CHECKER.is_monotonic_with_skip(seq)
                    || Self::INC_CHECKER.is_monotonic(&seq[1..])
                    || Self::DEC_CHECKER.is_monotonic_with_skip(seq)
                    || Self::DEC_CHECKER.is_monotonic(&seq[1..])
            })
            .count() as _
    }
}

/// Encapsulates monotonicity check logic with a fixed max step and ordering.
#[derive(Copy, Clone)]
struct MonotonicChecker {
    max_step: i32,
    order: Ordering,
}

impl MonotonicChecker {
    /// Checks if sequence is strictly monotonic with configured order and step.
    fn is_monotonic(&self, sequence: &[i32]) -> bool {
        sequence
            .windows(2)
            .all(|pair| self.is_valid_pair(pair[0], pair[1]))
    }

    /// Allows skipping at most one element to restore monotonicity.
    fn is_monotonic_with_skip(&self, sequence: &[i32]) -> bool {
        sequence.len() <= 2 || {
            let first = sequence[0];
            let second = sequence[1];
            let third = sequence[2];

            if self.is_valid_pair(first, second)
                && self.is_monotonic_with_skip(&sequence[1..])
            {
                return true;
            }

            self.is_valid_pair(first, third)
                && self.is_monotonic(&sequence[2..])
        }
    }

    /// Checks if two values satisfy order and max step constraint.
    fn is_valid_pair(&self, a: i32, b: i32) -> bool {
        a.cmp(&b) == self.order && (a - b).abs() <= self.max_step
    }
}

Tags: #rust #adventofcode #recursion