Advent of Code 2024 - Day 2, in Kotlin - Red-Nosed Reports
Kotlin solutions to parts 1 and 2 of Advent of Code 2024, Day 2: 'Red-Nosed Reports'
Today we get to see a few bits of the Kotlin Standard Library that we haven’t seen yet. One of my favorites - zipWithNext()
makes an appearance, as do some index-related functions. We will also take advantage of the fact that early Advent of Code puzzles can be brute forced, whereas later ones tend to make that impractical.
If you’d rather just view the code, my GitHub Repository is here.
Puzzle Input
Today’s puzzle input will come into our Day02
class as a List<String>
and get parsed into a List<List<Int>>
. Originally I had attempted to parse each row into an IntArray
, but IntArray
lacks a function we need later, so List<Int>
it is.
class Day02(input: List<String>) {
private val reports: List<List<Int>> = parse(input)
private fun parse(input: List<String>): List<List<Int>> =
input.map { row ->
row.split(" ").map { it.toInt() }
}
}
This structure is fairly common for Advent of Code - take the input
, map
each row
, split
the row
, and then map
each split part to an Int
. Sometimes we want Long
instead, but today’s input doesn’t seem to require it (mine didn’t, anyway).
⭐ Day 2, Part 1
The puzzle text can be found here.
To solve part 1, we need to find the differences between each consecutive pair of numbers in the list, so we’ll use zipWithNext
to provide those pairs. Each pair is then mapped to the difference between them, so diffs
ends up as List<Int>
whose values are the differences between consecutive numbers in the report
.
Once we have the diffs
calculated, we can see if all
of them are either in the range of 1
to 3
or -1
to -3
. Note that Kotlin makes us express the negative range from most lowest to highest. If either of these things is true, the report
is safe.
// In Day02
private fun isSafe(report: List<Int>): Boolean {
val diffs = report.zipWithNext().map { (left, right) -> right - left }
return diffs.all { it in 1..3 } || diffs.all { it in -3..-1 }
}
I want to point out that despite the fact that we use zipWithNext()
here, we could have done this a few ways. Kotlin has a windowed()
function which slides a window over an Iterable
(like our List<Int>
) and emits a series of List<Int>
s. We could have use that in place of zipWithNext()
and fixed map
to use the list instead of destructuring the Pair
. We could also have done this via indexing: get each index (via indices
), drop the first one, and then map the index
to report[index]-report[index-1]
. There are probably a couple more ways to do this if we sat and thought about it. I just liked zipWithNext()
the most because to me it conveys what we’re doing here more directly.
Using the handy count
function on our reports
list helps solve part 1:
// In Day02
fun solvePart1(): Int =
reports.count { isSafe(it) }
Star earned! Onward!
⭐ Day 2, Part 2
The puzzle text can be found here.
There might be a really elegant way to solve this puzzle using math, or by some analysis of the input, but today we’re going to brute force our answer. The puzzle input is small, we ideally run this one time in our life, and it is quick to write and explain. Brute force it is!
We’ll approach this by writing a new function called isSafeDampened
. This new function will remove each element of the report
and see if it is safe by calling isSafe
. If removing any index yields a safe report, we’re done.
// In Day02
private fun isSafeDampened(report: List<Int>): Boolean =
report.indices.any { removeThis ->
isSafe(report.filterIndexed { index, _ -> removeThis != index })
}
There are two interesting functions in the Kotlin Standard Library being used here. First is indices
, which emits each of the indices of an Iterable
. This is handy and can be used any time you’re tempted to set up a range to go over a List
or something like that. We also see filterIndexed
which will generate a new List<Int>
from report
with an element at the removeThis
index removed. We pass these new lists one by one to isSafe
and see if any
of them are safe when dampened.
And similarly to part 1, we count
the number of reports
that meet the new criteria:
// In Day02
fun solvePart2(): Int =
reports.count { isSafeDampened(it) }
Star earned! See you tomorrow!
Further Reading
- Index of All Solutions - All posts and solutions for 2024, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 2
- Advent of Code - Come join in and do these challenges yourself!