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