Skip to Content

Advent of Code 2021 - Day 1, in Kotlin - Sonar Sweep

Kotlin solutions to parts 1 and 2 of Advent of Code 2021, Day 1: 'Sonar Sweep'

Posted on

Advent of Code is back, and for the fifth year in a row, I’m going to attempt to solve each day’s puzzles with an idiomatic and clear Kotlin solution. For those of you who haven’t read my posts in years past, there are some caveats. First, I aim to deliver a clear solution that is easy to understand (and explain!). That means sometimes these solutions won’t be optimized for speed/memory/whatever. I’m optimizing for clarity. I feel like that’s fine, given these are puzzles and not production code with a time budget associated with them. Second, I have a job and a family, so on certain days when I’m traveling or have a deadline, solutions may be delayed. Especially since I am in US/Eastern timezone and don’t even read the puzzle until morning (midnight is way past my bedtime).

Now that that’s out of the way… I had fun with today’s puzzle.

If you’d rather just view code, the GitHub Repository is here .

Problem Input

I am not going to cover how I load input file and convert it into a List<Int>. If you are interested in that, please check out Resources.kt in my GitHub repository. It’s mostly the same as in previous years, and is a set of helper functions I use to load the file and do only the most basic type conversions. Any real parsing that we need to do for our puzzle solutions will generally be explained in this section.

Today, however, we can declare our input as a List<Int> and get started.

class Day01(private val input: List<Int>) {
  // TODO
}

⭐ Day 1, Part 1

The puzzle text can be found here.

Now that we have our input as a List<Int>, we want to compare each element to its neighbor. There are a lot of ways to handle a problem like this, but my favorite is zipWithNext. So much so that at my job there was a running joke about how I would solve each of last year’s puzzles with zipWithNext. Seriously, it is one of the most useful functions in the Kotlin standard library for puzzles like this, or code that has to do data manipulation.

In our case zipWithNext will turn our List<Int> into a List<Pair<Int,Int>>. In plain English, zipWithNext pairs up each of the elements in our list. Suppose we had a list of (1, 2, 3, 4). By calling zipWithNext on that list, we would end up with a list of four Pairs: ([1, 2], [2, 3], [3, 4]). Three nice neat pairs of integers, just like we want.

Once our elements are paired up, we can count how many of them match the criteria (number is increasing) using the count function, also from the Kotlin standard library. The count function takes a predicate and if that returns true, the counter goes up by one. In our case, we are comparing if the first element in the pair is less than the second element in the pair:

// In Day01

fun solvePart1(): Int =
    input
        .zipWithNext()
        .count { it.first < it.second }

Run this function with our input and you should get the correct answer to Part 1.

Destructuring

One other way to write this might be to destructure the Pair<Int,Int> into individual Ints in the count predicate:

// Alternate solution

fun solvePart1(): Int =
    input
        .zipWithNext()
        .count { (first, second) -> first < second } 

If you don’t like using it.first and it.second in the count predicate, you can destructure them into more useful variables that are local to that predicate. In this case, I called them first and second, but you can call them whatever you’d like. Destructuring is supported in certain places in Kotlin, and by certain objects (conveniently Pair is one of them) and eliminates the need to reference the object before using its properties. The fields in the object (our Pair) are pulled out and assigned to our variables (first, and second).

Either way solves the problem, and it’s a matter of personal preference as to whether you destructure here or not. I just wanted you to see what it does in case we end up having to use it in a future puzzle solution.

Star earned, onward!

⭐ Day 1, Part 2

The puzzle text can be found here.

Part two is quite similar to part one, and lets me introduce windowed, another function from the Kotlin standard library that I find useful. The windowed function lets us create a sliding window over an Iterable (which our List<Int> implements). We can control the size of the window, the step (how many elements to move over when creating the next window), and whether we want partial windows or not (this defaults to false, whole windows only).

In our code, this will turn our List<Int> into a List<List<Int>>. In practical terms, lets suppose we have a list (1, 2, 3, 4, 5). By calling windowed(3, 1) on it (meaning: window size 3, step over 1 each time): we would end up with this list: ((1, 2, 3), (2, 3, 4), (3, 4, 5)). Neat, huh? This is a very convenient function and I suspect we’ll see it in future puzzle solutions.

Now we’re ready to write our solution:

// In Day01

fun solvePart2(): Int =
    input
        .windowed(3, 1) { it.sum() }
        .zipWithNext()
        .count { it.first < it.second }

Let’s go over that. First we use the windowed function to turn our List<Int> into a List<List<Int>>. Because we want the sum of the values in these inner List<Int>, we’ll map them to their sums using the optional transform function we can use with windowed. Conveniently, Kotlin provides sum() on Iterable<Int> (reminder: List<Int> implements Iterable<Int>) for us, which does the work. At this point, we’ve reduced part two to what we started with in part one. If we pair up our sums with zipWithNext, we can count which ones are increasing and return that as our answer!

That’s two stars for today!

My Challenge To You!

My challenge to you today is to figure out how to refactor parts one and two into a single function. The solutions are very similar and I can think of a couple of ways we could refactor this. If you do, let me know. I’d love to see what you come up with!

Further Reading

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