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

Задача третьего дня Advent of Code — Mull It Over.

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

Необходимо выделить все корректные mul(X,Y) и подсчитать сумму произведений.

💡 Идея

Парсим только строки, точно соответствующие регулярному выражению "mul(\d{1,3},\d{1,3})", остальные пропускаем. После чего результат каждого умножения суммируем.

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

Кроме mul, в дампе встречаются do() и don't():

По умолчанию mul разрешён. Надо подсчитать только те mul, которые активны в момент встречи.

💡 Идея

Добавим поддержку do() и don't() в парсер.
При проходе по потоку команд будем отслеживать флаг allow.
Регулярное выражение будет единым и поддерживать все три типа команд: do(), don't(), mul(a,b).

⚙️ Детали подхода

  1. Используем регулярное выражение с двумя альтернативами:
    • "(do|don't)\(()()\)" — ловит do() и don't(), возвращает пустые аргументы
    • "(mul)\((\d{1,3}),(\d{1,3})\)" — ловит mul(x,y) с числами от 1 до 999
  2. Используем метод extract() из regex::Captures, чтобы получить тройку: cmd, arg0, arg1
  3. Парсинг команд реализован в виде метода command_from_caps внутри CommandScanner
  4. Итерация над командами внутри solve_hard() реализована через .fold(...), что устраняет необходимость в мутабельных переменных

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

Пусть n — длина строки mem_dump, k — количество валидных команд.

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

use super::TaskData;
use regex::{Captures, Regex};

pub struct Solution {}

enum Command {
    Mul(i32, i32),
    Do,
    Dont,
}

struct CommandScanner {
    re: Regex,
}

impl Solution {
    /// Part I: sum all valid mul(a,b) instructions
    pub fn solve_simple(data: &TaskData) -> i32 {
        CommandScanner::new()
            .scan(&data.mem_dump)
            .filter_map(|cmd| match cmd {
                Command::Mul(a, b) => Some(a * b),
                _ => None,
            }).sum()
    }

    /// Part II: obey do() / don't() switches to enable/disable mul
    pub fn solve_hard(data: &TaskData) -> i32 {
        CommandScanner::new()
            .scan(&data.mem_dump)
            .fold((0,true), |(acc, allow), cmd| match cmd {
                Command::Do => (acc, true),
                Command::Dont => (acc, false),
                Command::Mul(a, b) => (acc + if allow { a*b } else {0}, allow),
            }).0
    }
}

impl CommandScanner {
    // Unified regex: exactly matches do(), don't(), or mul(X,Y) without spaces
    const RE_COMMAND: &str = r"(do|don't)\(()()\)|(mul)\((\d{1,3}),(\d{1,3})\)";

    pub fn new() -> Self {
        Self {re: Regex::new(Self::RE_COMMAND).unwrap()}
    }

    /// Parses input into a stream of valid Command items
    pub fn scan<'a>(&'a self, input: &'a str) -> impl Iterator<Item = Command> + 'a {
        self.re.captures_iter(input).filter_map(Self::command_from_caps)
    }

    /// Converts a regex capture into a Command
    fn command_from_caps(cap: Captures) -> Option<Command> {
        let (_, [cmd, arg0, arg1]) = cap.extract();

        match cmd {
            "do" => Some(Command::Do),
            "don't" => Some(Command::Dont),
            "mul" => Some(Command::Mul(arg0.parse().ok()?, arg1.parse().ok()?)),
            _ => None,
        }
    }
}

Tags: #rust #regex #adventofcode