Advent of Code 2025 - Day 5, in Kotlin - Cafeteria
Kotlin solutions to parts 1 and 2 of Advent of Code 2025, Day 5: 'Cafeteria'
I love how in Advent of Code we can drive a forklift through a wall so our Elf friends can get to the cafeteria more easily, completely unconcerned about collateral damage or injury. What I find even more amusing is that the Elves find this normal enough to just hand us some new problem to solve (which is not the jagged hole in the cafeteria wall, somehow).
This puzzle feels very familiar but I can’t seem to find anything like it in my past write-ups or notes. Today we’ll implement a function to combine two adjacent or overlapping ranges (something I really feel like we’ve done before but cannot find), to solve both parts of the puzzle.
If you’d rather just view the code, my GitHub Repository is here.
Puzzle Input
Since our input is in two sections separated by a blank line, we’ll have to have two solutions for parsing it. For the ranges, which come first in the input, we’ll implement that logic in parseRanges. Basically, we look at all the input until we run into a blank line, and then stop. For each line of input, we split it on -, convert the first and last values to Long, and then create a LongRange from them using the .. operator.
We’ll cover combineRanges below.
For the ingredients, we drop all of the rows until we run into a blank, then drop the blank row, mapping the remaining rows to Long.
class Day05(input: List<String>) {
private val ranges: List<LongRange> = parseRanges(input)
private val ingredients: List<Long> = input.dropWhile { it.isNotBlank() }.drop(1).map { it.toLong() }
private fun parseRanges(input: List<String>): List<LongRange> =
input
.takeWhile { it.isNotBlank() }
.map {
val (first, last) = it.split("-")
first.toLong()..last.toLong()
}
.combineRanges()
}
If you look at the ranges we’re given there is quite a bit of overlap between them. For part 1, this doesn’t matter very much, it just makes us do a few more membership tests (“is this ingredient in this range?"). But for part 2, we’ll need these ranges to not overlap. So let’s write a function to combineRanges and remove all overlaps.
// In Extensions.kt
fun Iterable<LongRange>.combineRanges(): List<LongRange> =
buildList {
this@combineRanges.sortedBy { it.first }
.fold(null as LongRange?) { previous, current ->
when {
previous == null -> current
current.first > previous.last + 1 -> {
add(previous)
current
}
else -> previous.first .. maxOf(previous.last, current.last)
}
}?.let { add(it) }
}
We’ll return our new LongRange objects in an ordered List, by using buildList. First we sort the incoming ranges by their first values, putting them in increasing order. Next, we set up a fold over all the ranges, seeding it with a null LongRange.
For each current range we encounter, we check if this is the first range we’ve seen (previous is null). If that test fails, we check to see if the current range does not overlap the previous range, and if not, add the previous to the list we’re building and return the current from our fold lambda. The only remaining case means that previous and current overlap, so create a new LongRange from the first element of the previous (which is guaranteed to be less than or equal to current’s first value) and the maximum value of the last values of previous and current.
We add the final value returned by the fold to the List<LongRange> if it is not null (which can only happen if the origination list is empty).
⭐ Day 5, Part 1
The puzzle text can be found here.
Since we’ve parsed our ranges and ingredients, we can count the number of ingredients that appear in any range, giving us the answer for part 1.
// In Day05
fun solvePart1(): Int =
ingredients.count { ingredient -> ranges.any { ingredient in it } }
Star earned! Onward!
⭐ Day 5, Part 2
The puzzle text can be found here.
Because our ranges are non-overlapping, we can get the sumOf each of their sizes. LongRange doesn’t have a size or length function, so we will have to calculate this ourselves using first and last for each range, taking care to add 1 as the ranges are inclusive.
// In Day05
fun solvePart2(): Long =
ranges.sumOf { it.last - it.first + 1 }
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 5
- Advent of Code - Come join in and do these challenges yourself!