Skip to Content

Advent of Code 2024 - Day 1, in Kotlin - Historian Hysteria

Kotlin solutions to parts 1 and 2 of Advent of Code 2024, Day 1: 'Historian Hysteria'

Posted on

It’s the first of December and that can only mean one thing - Advent of Code is back! Can you believe Advent of Code is in its tenth year? I’ve said it before, but I am always so impressed with the level of effort that goes into bringing us this set of puzzles every year. Eric Wastl (AoC’s creator) deserves all of the credit in the world, please consider donating to Advent of Code if it is within your means.

This will be my eighth year blogging solutions in Kotlin. Every year I worry how I’ll get it all done, and this year is no different. There will almost certainly be days that I won’t finish the solution or the write-up by the end of the day. That’s fine, I’ll eventually get them all written and posted. Family and work take priority over Advent of Code.

Since Kotlin 2.1 just came out, I’ll be using that. All of my solutions will be published to a GitHub Repository .

Before We Start: A Note on AI

I will be doing these puzzles without using AI. I code with IntelliJ and do not have AI Assistant or any other AI-based plugins enabled. Personally, I find this kind of built-in tool distracting when I code, and it is far more rewarding for me if I can come up with a solution on my own. I also commit to not asking non-integrated AIs (ChatGPT, Claude, etc.) for help.

Occasionally, I’ll get help from somebody on the Kotlin Language Slack or on Reddit and in those cases I will call out that I got help and from where. This commitment extends all the way through my effort from solving the puzzles, to optimizing my solution, to writing these blog posts.

If you like using these tools, please don’t take this as any kind of judgement on your efforts from me. You code how you code, and that’s great.

Puzzle Input

As in previous years, I have a set of functions to help wrangle puzzle inputs into a useable format. Mostly just turning files into List<String> or String with delimiters, nothing more. If you’ve seen this in the past it is no different this year, I took it as-is and changed the package name. We’ll use these functions to take our input today as a List<String> where each String is one row of text from the puzzle file.

// In Day01
class Day01(private val input: List<String>) {

    private fun parse(input: List<String>): Pair<List<Int>, List<Int>> {
        val left = mutableListOf<Int>()
        val right = mutableListOf<Int>()
        input.forEach {
            left.add(it.substringBefore(" ").toInt())
            right.add(it.substringAfterLast(" ").toInt())
        }
        return left to right
    }
}

We will write our parse function so it returns a Pair with each element being a List<Int>. This will allow us to easily destructure these lists when we use them in a moment. To create the lists, we define left and right as mutable lists so we can add to them. We then go through each line of the input and pick out the parts we need. We could have written this with a regular expression, but I chose to use substringBefore and substringAfterLast from the Kotlin Standard Library instead. Since we’ll be doing math with these numbers, we’ll turn the resulting strings into integers via toInt. We can use Kotlin’s infix to function to turn our left and right lists into a Pair<List<Int>, List<Int>>.

⭐ Day 1, Part 1

The puzzle text can be found here.

Since our parse function reruns a Pair, we can destructure it back out into a left and right list. We’ll do this in a let, so we can have a nice single expression to return from solvePart1. Since left and right are unsorted, we’ll sort them via sorted, which sorts them and returns a new list rather than sorting them in place. We can zip them together, which gives us a series of Pair<Int,Int>.

// In Day01

fun solvePart1(): Int =
    parse(input).let { (left, right) ->
        left.sorted()
            .zip(right.sorted())
            .sumOf { (it.first - it.second).absoluteValue }
    }

Finally, we can get the sumOf all of the differences in the first and second elements of the pairs. We’ll keep the math simple by subtracting second from first, even if it is bigger and taking the absoluteValue of the result.

Returning this sum gives us the answer to part 1.

Star earned! Onward!

⭐ Day 1, Part 2

The puzzle text can be found here.

For Part 2, the parsing and the let function with destructuring is the same, but now we have to calculate frequencies for the right list. Kotlin Standard Library to the rescue! We can use the built-in groupingBy function, paired with eachCount to give us a Map<Int,Int> where the key is the number and the value is the number of times it has appeared. We’ll store this in a map called frequencies.

// In Day01

fun solvePart2(): Int =
    parse(input).let { (left, right) ->
        val frequencies = right.groupingBy { it }.eachCount()
        left.sumOf { it * frequencies.getOrDefault(it, 0) }
    }

We can get the sumOf all of the left values multiplied by their frequencies. Keep in mind that not every value is in the frequencies map, so we use getOrDefault with a default of 0 so as not to get an exception or a nullable integer.

Returning this sum gives us the answer to part 2.

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 1
  4. Advent of Code - Come join in and do these challenges yourself!