Skip to content

AOC 2024, Day 02: Red-Nosed Reports

Day 2 of Advent of Code 2024, Red-Nosed Reports, involves analyzing reactor data to determine safe reports. In this post, we'll share solutions in both C# and F# for solving the puzzle.

Elf Shenanigans

We are given reports with levels. Each report must follow two rules to be considered safe:

  1. The levels must be monotonic, meaning they must either be all increasing or all decreasing.
  2. The difference between adjacent levels must be between 1 and 3.

Part 1: Checking for Safe Reports

We need to determine how many reports are safe by verifying the above two conditions.

Part 2: Accounting for the Problem Dampener

The Problem Dampener allows us to remove one level from an unsafe report to make it safe. We need to adjust the analysis to check if removing one level makes the report safe by ensuring it becomes monotonic and follows the difference rule.

Santa's Ingenious Solutions

using AdventOfCode.Lib;
using Pair = (int Left, int Right);

namespace AdventOfCode.Y2024;

[AocPuzzle(2024, 2, "Red-Nosed Reports", "C#")]
public sealed class Day02 : IAocDay<int>
{
    public static int Part1(AocInput input) => 
        input.Lines
            .Pipe(lines => Instructions(lines).Count(Monotonic));

    public static int Part2(AocInput input) =>         
        input.Lines
            .Pipe(lines => 
                Instructions(lines).Count(instructions => ProblemDampener(instructions).Any(Monotonic))
            );

    private static bool Monotonic(int[] instructions) =>
        instructions.Zip(instructions.Skip(1), (l, r) => new Pair(l, r))
            .ToArray()
            .Pipe(pairs => MonotonicIncreasing(pairs) || MonotonicDecreasing(pairs));

    private static bool MonotonicIncreasing(Pair[] pairs) =>
        pairs.All(p => p.Right - p.Left is >= 1 and <= 3);

    private static bool MonotonicDecreasing(Pair[] pairs) =>
        pairs.All(p => p.Left - p.Right is >= 1 and <= 3);

    private static IEnumerable<int[]> ProblemDampener(int[] instructions) =>
        from i in Enumerable.Range(0, instructions.Length + 1)
        let take = instructions[..Math.Max(0, i - 1)]
        let skip = instructions[i..]
        select (int[])[..take, ..skip];

    private static IEnumerable<int[]> Instructions(IEnumerable<string> lines) => 
        lines.Select(l => l.Split(' ').Select(int.Parse).ToArray());
}
open AdventOfCode.Lib

[<AocPuzzle(2024, 2, "Red-Nosed Reports", "F#")>]
module Fay02 =

    let instructions (lines: string seq) =
        lines |> Seq.map (fun line -> line.Split ' ' |> Array.map int)

    let monotonicIncreasing (pairs: (int * int) array) =
        pairs
        |> Array.forall (fun (left, right) -> 1 <= right - left && right - left <= 3)

    let monotonicDecreasing (pairs: (int * int) array) =
        pairs
        |> Array.forall (fun (left, right) -> 1 <= left - right && left - right <= 3)

    let monotonic (instructions: int array) =
        let left = instructions |> Array.take (Array.length instructions - 1)
        let right = instructions |> Array.skip 1
        let pairs = Array.zip left right

        (monotonicIncreasing pairs) || (monotonicDecreasing pairs)

    let problemDampener (instructions: int array) =
        seq {
            for i in 0 .. Array.length instructions do
                let take = instructions |> Seq.take (max 0 (i - 1))
                let skip = instructions |> Seq.skip i
                yield Seq.append take skip |> Seq.toArray
        }

    let part1 (input: AocInput) =
        input.Lines |> instructions |> Seq.filter monotonic |> Seq.length

    let part2 (input: AocInput) =
        input.Lines
        |> instructions
        |> Seq.filter (fun instruction -> problemDampener instruction |> Seq.exists monotonic)
        |> Seq.length
from aoc.core import AocInput
from dataclasses import astuple, dataclass
from typing import Iterator


@dataclass(frozen=True, slots=True)
class Pair:
    left: int
    right: int

    def __iter__(self):
        return iter(astuple(self))


def instructions(lines: list[str]) -> list[list[int]]:
    return [list(map(int, l.split(" "))) for l in lines]


def monotonic_increasing(pairs: list[Pair]) -> bool:
    return all(1 <= (r - l) <= 3 for l, r in pairs)


def monotonic_decreasing(pairs: list[Pair]) -> bool:
    return all(1 <= (l - r) <= 3 for l, r in pairs)


def monotonic(instructions: list[int]) -> bool:
    pairs = [Pair(ls, rs) for ls, rs in zip(instructions, instructions[1:])]
    return monotonic_increasing(pairs) or monotonic_decreasing(pairs)


def problem_dampener(instructions: list[str]) -> Iterator[list[int]]:
    for i in range(len(instructions) + 1):
        yield instructions[: max(0, i - 1)] + instructions[i:]


def part1(input: AocInput) -> int:
    return sum(monotonic(i) for i in instructions(input.lines))


def part2(input: AocInput) -> int:
    return sum(any(monotonic(i) for i in problem_dampener(instr)) for instr in instructions(input.lines))

Santa's & Elves' Reflections

Day 2 of Advent of Code 2024 involved analyzing reactor reports based on level changes. By checking for monotonicity and valid differences, we can determine safe reports. The Problem Dampener adds flexibility by allowing the removal of one level to make an unsafe report safe.

Check out the repository for solutions: AdventOfCode & AdventOfCode.Lib.