# Advent of Code 2023 - Day 1, in Kotlin - Trebuchet?!

Kotlin solutions to parts 1 and 2 of Advent of Code 2023, Day 1: 'Trebuchet?!'

Posted on

Hello again! Another December 1st means the start of a new season of Advent of Code! I look forward to this every year because I have so much fun solving the puzzle and comparing my solutions to those of my friends, and a bunch of random strangers on the internet.

For the past six years I have made a serious effort to solve the puzzle and blog about it on the day it was revealed. However, because of the significant demand on my time that takes, I’ve decided to let myself relax that stance a bit this year. I will try my best to solve and blog each puzzle each day. However, given that I have just started a new job and have several personal obligations during the month of December, I am going to just give a best-effort approach. So we’ll see how well I do. I am going to try and embrace the concept of Eventual Consistency and get these blog posts done eventually, ideally not too far after the puzzle is revealed. Thanks for understanding.

As in past years, I will put my solutions in a GitHub Repository .

Let’s get started!

JetBrains Live Stream

I was fortunate enough to be invited to the JetBrains Live Stream today to go over my solution to today’s puzzle. Check it out, and be sure to check it out for the first 12 days of the Advent of Code. I’ve seen the secret guest list and they’ve got some great folks lined up to solve these puzzles on-air. I think you’ll really enjoy yourself.

Also, JetBrains are running an Advent of Code contest with some nice prizes. Details are in the video as well.

Puzzle Input

Each day we’ll have a class named after the day number (`Day01` in our case today) which will have an `input` parameter that represents the puzzle input. In our case, this input comes from the unit test that runs the actual puzzle and tests the solution for correctness. The type of `input` may change from day to day. Today, for example, it is a `List<String>` representing the entire puzzle input. Other days it might be a `String` or `List<Int>`, depending on how we’ll use it when writing the solution. The work of loading the daily input file and possibly turning it into a `List` is done by a helper class class called `Resources` . which I’ve used in the past.

Let’s set up our `Day01` class, taking the puzzle `input` as a `List<String>`, which we will make a private property that we can use througout our class. We can take this input as-is since we don’t need to do any additional processing on it.

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

}
``````

#### ⭐ Day 1, Part 1

The puzzle text can be found here.

Part 1 asks us to find some integers in each row of input, combine them, and sum them all together. For this, I’ve decided to use `first` and `last` from the Kotlin Standard Library which will take a lambda and (in our case) return the `first` or `last` digit in each `row` of input. Since these are `Character`s, we will need to concatenate them before turning them into an `Int` via `toInt`. To concatenate, I’ve chosen to make the whole expression a `String`. An alternative approach would have been to convert each `Character` to an `Int`, multiply the `first` one by `10`, and add them together.

``````// In Day01

private fun calibrationValue(row: String): Int =
"\${row.first { it.isDigit() }}\${row.last { it.isDigit() }}".toInt()
``````

Next, we use `sumOf` over the `input` to add all of the calibration values together.

``````// In Day01

fun solvePart1(): Int =
input.sumOf { calibrationValue(it) }
``````

Star earned! Onward!

#### ⭐ Day 1, Part 2

The puzzle text can be found here.

Part 2 might seem tricky on the surface, but we’ll work through it and hopefully come up with a good solution. The first thing we’ll need to do is make a `Map<String, Int>` to store the mapping from a word to its value. We could have made the map values `Character`s, but I kept them as `Int` because I liked the way it highlighted in my editor. :)

``````// In Day01
private val words: Map<String, Int> = mapOf(
"one" to 1,
"two" to 2,
"three" to 3,
"four" to 4,
"five" to 5,
"six" to 6,
"seven" to 7,
"eight" to 8,
"nine" to 9
)
``````

Before we use our map let’s stop and think. Given an input row, we really only care when an individual `Character` is a digit, or is the start of a word that could spell a digit. Any other letter of any kind can be ignored.

To accomplish this, we’ll use `sumOf` over each `row` of `input` after we calculate the `calibrationValue` just like in Part 1. This time we’ll need to modify the `row` so that the call to `calibrationValue` works properly. For this, we’ll look at each `Character` of the `row` using `mapIndexedNotNull` which gives us each `Character` of a `String` and its `index` value.

We’ll look at each `Character` and if it is a digit, we just accept it into the eventual `String` we end up creating. No more work to do in that case. The next case is more interesting! We will call our `possibleWordsAt` function (see below) in order to get all of the `String`s starting at this position in the `row` (via the `index`) that could be a word representing a digit. Once we have the `List<String>` that represents possible number-words, we loop through them and see if any of them are in the `words` map. If they are, the call to `words[candidate]` will return a non-null value, which we’ll add to our `String`. If it returns a null (the value is not in the map), we can keep looping. This shows off my favorite Kotlin Standard Library function - `firstNotNullOfOrNull`, which roughly translates to “run this lambda over the input until you get a non-null value and then stop, and if you don’t ever find a value, return null”. So for example, if one word returns “two”, we’ll get a 2 from the `words` map and add it to our `String`.

Since we end that call with a `List<String>`, we will join them into a single `String` via `joinToString`.

``````// In Day01
fun solvePart2(): Int =
input.sumOf { row ->
calibrationValue(
row.mapIndexedNotNull { index, c ->
if (c.isDigit()) c
else
row.possibleWordsAt(index).firstNotNullOfOrNull { candidate ->
words[candidate]
}
}.joinToString()
)
}
``````

All that’s left is to write our `possibleWordsAt` function. I decided to write this as an extension function, but it doesn’t really have to be if you prefer not to. Either way, we only need to consider the next 3 to 5 characters because the words are at least 3 characters long and at most 5. We loop over the range of 3 to 5 and take a `substring` starting at the `startingAt` point, and extending at most to the end of the `String`. To make sure we don’t ask for a `substring` larger than we can get, we call `coerceAtMost` from the Kotlin Standard Library. Note that there is a corresponding `coerceAtLeast` which does the opposite.

``````// In Day01

private fun String.possibleWordsAt(startingAt: Int): List<String> =
(3..5).map { len ->
substring(startingAt, (startingAt + len).coerceAtMost(length))
}
``````

Calling `solvePart2()` should now give us the correct answer.

Star earned! See you tomorrow (hopefully)!