Advent of Code 2024 Day 2

Day two! Today’s challenge is about checking the safety of some kind of report. The report is a sequence of numbers, and we need to check if it is “safe”. A report is considered safe if it follows these criteria:

  • The report is either strictly ascending or strictly descending.
  • The difference between any two adjacent numbers in the report is at most 3.

And step two added an additional criteria:

  • If we remove any SINGLE number from the report, the remaining sequence is still be safe.

Pretty simple! So, I wrote a function checkReportIsSafe that takes an array of numbers and returns true if the report is safe, and false otherwise. The function first checks the report without removing any numbers, and if it fails, it tries removing each number one at a time and checking the report again.

Here’s the code:

import {reports} from './input';

function checkReportIsSafe(report: Array<number>): boolean {
  /**
   * Helper function that validates a sequence without using the dampener.
   * Returns true if the sequence follows all safety criteria.
   */
  function isSequenceValid(sequence: Array<number>): boolean {
    // Single measurements or empty sequences are considered valid
    if (sequence.length < 2) return true;

    // Establish initial direction of sequence
    let direction: 'asc' | 'desc' | undefined;
    if (sequence[1] > sequence[0]) {
      direction = 'asc'; // Ascending sequence detected
    } else if (sequence[1] < sequence[0]) {
      direction = 'desc'; // Descending sequence detected
    }

    // Validate each pair of adjacent measurements
    for (let i = 1; i < sequence.length; i++) {
      const current = sequence[i];
      const previous = sequence[i - 1];

      // Ensure consistent direction throughout sequence
      if (current > previous) {
        if (direction === 'desc') return false; // Direction violation
        direction = 'asc';
      } else if (current < previous) {
        if (direction === 'asc') return false; // Direction violation
        direction = 'desc';
      }

      // Validate step size between measurements
      const difference = Math.abs(current - previous);
      if (difference > 3 || difference === 0) return false; // Step size violation
    }

    return true; // All validation checks passed
  }

  // First attempt: Validate sequence without dampening
  if (isSequenceValid(report)) return true;

  // Second attempt: Try removing each measurement one at a time
  // This implements the Problem Dampener functionality
  for (let i = 0; i < report.length; i++) {
    const dampened = [...report.slice(0, i), ...report.slice(i + 1)];
    if (isSequenceValid(dampened)) return true;
  }

  // If all attempts fail, the report is unsafe
  return false;
}

// Calculate total number of safe reports
const correctReports = reports.filter(checkReportIsSafe).length;
console.log(`⛄ 🎅 DAY 2: There are ${correctReports} safe reports`);

Once again input.ts contains the input from the challenge, and a small helper function to turn it into an array:

const parseToArrays = input => {
  // Split by newlines and filter out empty lines
  const lines = input
    .trim()
    .split('\n')
    .filter(line => line.trim());

  // Convert each line into an array of numbers 💤
  return lines.map(line =>
    line
      .trim()
      .split(' ')
      .filter(num => num.trim()) // Remove empty strings
      .map(num => parseInt(num, 10))
  );
};