Skip to Content

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'

Posted on

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

  1. Index of All Solutions - All posts and solutions for 2024, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 2
  4. Advent of Code - Come join in and do these challenges yourself!