Skip to main content

Day 2: Red-Nosed Reports

Puzzle description

https://adventofcode.com/2024/day/2

Solution summary

  • First we parse each line of the input into a Report case class: case class Report(levels: Seq[Long])
  • In each Report, we construct the sequence of consecutive pairs.
  • For part 1, we check if the pairs are all increasing or all decreasing, and if the difference is within the given limits (such a report is "safe").
  • For part 2, we construct new Reports obtained by dropping one entry in the original report, and check if there exists a safe Report among these.
  • In both parts, we simply count the number of Reports that are considered safe.

Parsing

Each line of input is a string of numbers separated by a single space. Therefore parsing looks like this:

case class Report(levels: Seq[Long])

def parseLine(line: String): Report = Report(line.split(" ").map(_.toLong).toSeq)

def parse(input: String): Seq[Report] = input
.linesIterator
.map(parseLine)
.toSeq

Part 1: methods of the Report case class

We need to check consecutive pairs of numbers in each report in 3 ways:

  • to see if they are all increasing,
  • to see if they are all decreasing,
  • to see if their differences are within given bounds.

So let's construct them only once, save it as a val, then reuse this value 3 times. It's not the most efficient way (like traversing only once and keeping track of everything), but it's very clean and simple:

case class Report(levels: Seq[Long]):
val pairs = levels.init.zip(levels.tail) // consecutive pairs
def allIncr: Boolean = pairs.forall(_ < _)
def allDecr: Boolean = pairs.forall(_ > _)
def within(lower: Long, upper: Long): Boolean = pairs.forall: pair =>
val diff = math.abs(pair._1 - pair._2)
lower <= diff && diff <= upper
def isSafe: Boolean = (allIncr || allDecr) && within(1L, 3L)

Part 1 solver simply counts safe reports, so it looks like this:

def part1(input: String): Int = parse(input).count(_.isSafe)

Part 2

Now we add new methods to Report. We check if there exists a Report obtained by dropping one number, such that it's safe. We do this by iterating over the index of each Report. Then, a Report is safe, if it's safe as in Part 1, or one of the dampened reports is safe:

case class Report(levels: Seq[Long]):
// ... as before
def checkDampenedReports: Boolean = (0 until levels.size).exists: index =>
val newLevels = levels.take(index) ++ levels.drop(index + 1)
Report(newLevels).isSafe
def isDampenedSafe: Boolean = isSafe || checkDampenedReports

Again this is not the most efficient way (we are creating many new Report instances), but our puzzle inputs are fairly short (there are at most 8 levels in each Report), so it's a simple approach that reuses the isSafe method from Part 1.

Part 2 solver now counts the dampened safe reports:

def part2(input: String): Int = parse(input).count(_.isDampenedSafe)

Solutions from the community

Share your solution to the Scala community by editing this page. You can even write the whole article! See here for the expected format