Advent of Code 2025 - Day 2, in Kotlin - Gift Shop
Kotlin solutions to parts 1 and 2 of Advent of Code 2025, Day 2: 'Gift Shop'
It’s day 2 of Advent of Code and we’re already creating extension and higher-ordered functions! I took the “easy” route and converted most of the Long values we have into Strings, but I’m sure there’s a mathy way to do this without all of the conversion. If you use a different approach, let me know!
If you’d rather just view the code, my GitHub Repository is here.
Puzzle Input
Since the input is a series of ranges, we will represent each as a LongRange and store them in a List called ranges. To do the parsing, we first split on comma, then for each of those split again on the dash separator, and then map the resulting List<String> into a LongRange.
class Day02(input: String) {
private val ranges: List<LongRange> = parseInput(input)
private fun parseInput(input: String): List<LongRange> =
input.split(",")
.map { it.split("-") }
.map { LongRange(it.first().toLong(), it.last().toLong()) }
}
⭐ Day 2, Part 1
The puzzle text can be found here.
I try not to spoil part 2 while we’re still working on part 1, but both parts today have the same mechanic; get the sum of some of the numbers from each range. So we’ll write a generic solve function to set up that loop and do it for us. In this example, we take in a function which does the “something”. In our case, we make function be an extension function on Long that returns a Long?. The reason for a nullable Long? return type is a signal to the implementation that a null result means the Long member of a range does not exhibit the special properties we are looking for.
// In Day02
private fun solve(function: Long.() -> Long?): Long =
ranges.sumOf { range ->
range.mapNotNull { it.function() }.sum()
}
So now we need to write an extension function on Long to do the checking for part 1. The takeIf
from the Kotlin Standard Library is very handy in this case. Basically, it means “we want the value you called takeIf on (the range value in our case), if and only if the following check passes, otherwise return null”. The supplemental check works by turning our Long into a String, making sure the length of the resulting String is even (because odd-length values cannot match), and then compare the first half of the string to the last half.
// In Day02
private fun Long.part1InvalidOrNull(): Long? =
takeIf {
toString().let {
it.length % 2 == 0 && it.take(it.length / 2) == it.drop(it.length / 2)
}
}
Passing our part1InvalidOrNull() function to solve solves part 1!
// In Day02
fun solvePart1(): Long =
solve {
part1InvalidOrNull()
}
Star earned! Onward!
⭐ Day 2, Part 2
The puzzle text can be found here.
Since we wrote a generic solve function for part 1, we only need to worry about the extension function. This one still uses takeIf and converts the Long to a String, but the inner portion of this function is a bit different. We set up a range over the String going from size 1 to one half of the length of the String. Then, we test that any of those values (n), matches. We do that matching by calling the windowed function over the String, taking care to set both the window size and the number of spaces to move over to the same n value. We also specify that we are interested in partial ranges. If the resulting values all mean the same thing, we have a valid candidate.
// In Day02
private fun Long.part2InvalidOrNull(): Long? =
takeIf {
toString().let {
(1..it.length / 2).any { n ->
it.windowed(n, n, true).containsSingleValue()
}
}
}
Originally, I converted the List<String> result from windowed into a Set<String> and checked that the size is 1. However, that turned out to be somewhat slow, so I wrote my own containsSingleValue() function which appears to be much faster for this specific use case. Normally, I let things like that go in Advent of Code, aiming for clarity instead of clever, but I don’t think I’m sacrificing too much clarity here in exchange for a good speedup.
// In Day02
private fun <T> List<T>.containsSingleValue(): Boolean =
this.size == 1 || this.all { it == this.first() }
This implementation makes sure that all elements of the List are the same. Sure, there’s a redundant check of the first element to itself, and one could argue about whether an empty list contains all of the same values or not, but this works fine for this case. I’ll leave this function here, even though I suspect it would make a good generic extension function for future Advent of Code puzzles. We’ll move it if we need it again.
Just like in part 1, all that’s left is to call our function to solve!
// In Day02
fun solvePart2(): Long =
solve {
part2InvalidOrNull()
}
Star earned! See you tomorrow!
Further Reading
- Index of All Solutions - All posts and solutions for 2025, 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!