AoC 2024 Day 02: Red-Nosed Reports
Table of Contents
The reindeer need help analyzing some unusual data from the Red-Nosed reactor logs. Before diving in, have a read of the Day 02 story.
Input Data #
Each line of the input data is a report containing a space-separated list of integers called levels. Here we have an example containing six reports, each with five levels:
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
To read the input data we’ll use the readInput
function from the previous day, and then we’ll split each line into a list of integers all reports as a List<List<Int>>
.
fun extractReports(input: List<String>) = input.map { line ->
line.split(" ").map(String::toInt)
}
Now we can read the real input data and get on to solving part 1 of the puzzle.
Part 1 #
We need to determine which reports are safe. A report is deemed safe if it meets both of the following conditions:
- The levels are either all increasing or all decreasing
- Any two adjacent levels differ by at least one and at most three
fun isReportSafe(report: List<Int>) =
report.zipWithNext { a, b -> b - a }
.let { differences ->
differences.all { it in 1..3 } || differences.all { it in -3..-1 }
}
Taking a list of integers (the report), we use the zipWithNext
function to create a new list of pairs where each pair consists of the current element and the next element in the original list.
By default zipWithNext
forms pairs of adjacent elements in the list. For instance, if the report input was [1, 2, 3]
then zipWithNext
would result in [(1, 2), (2, 3)]
. As we want the difference between each pair, we provide a lambda function to calculate the differences.
In our lambda function (a, b) -> b - a
, a
is the current element and b
is the next element from the report list. It subtracts each element in the list from its succeeding element and returns a new list. For an initial list of [1, 3, 2]
, the new list would be [2, -1]
- the list of differences between adjacent elements.
Next, we take this differences list and check if all elements fall within either of the required ranges 1 to 3 or -3 to -1 returning true
where they are (safe) and false where they’re not (unsafe).
Part 2 #
Through the magic of a “Problem Dampener” we can now tolerate a single bad level in a report. So, it’s the same rules as part1, except if removing a single level from an unsafe report would make it safe, the report instead counts as safe.
fun isReportSafeWithDampener(report: List<Int>) =
isReportSafe(report) || report.indices.any { index ->
isReportSafe(report.filterIndexed { i, _ -> i != index })
}
Using the isReportSafe
function from part 1, we check if the given report is safe. If it is, the function immediately returns true. If not, the function proceeds to check whether the report would be safe if any of the readings (i.e., numbers) in the report were ignored. This simulates the effect of a dampener, which can disregard a faulty reading.
It does this by iterating through the indices of the report (report.indices.any
). For each index, it creates a new report that does not include the element at the current index (report.filterIndexed { i, _ -> i != index }
).
Then it checks this filtered report using the isReportSafe
function. If at least one of these filtered reports is safe, the .any
function will yield true and isReportSafeWithDampener
in turn will return true.
That’s it! The full source code is up on GitHub.