Skip to Content

Advent of Code 2023 - Day 15, in Kotlin - Lens Library

Kotlin solutions to parts 1 and 2 of Advent of Code 2023, Day 15: 'Lens Library'

Posted on

If you’d rather just view the code, my GitHub Repository is here.

Puzzle Input

Unlike most days this year, today we will take our input in as a single String and split it into instructions for later use.

class Day15(input: String) {

    private val instructions = input.split(',')

}

⭐ Day 15, Part 1

The puzzle text can be found here.

The only real code we need to write for part 1 is something to hash each instruction, which we will implement as an extension function.

// In Day15

private fun String.hash(): Int =
    this.fold(0) { carry, char ->
        ((carry + char.code) * 17).rem(256)
    }

Because we need to carry a value along after calculating each character value, we can use a fold, giving it the starting value of 0. Each iteration through the fold will give us a new char to evaluate and the carried value. We get the ASCII value of each char via the code property, multiply by 17, and take the remainder via the rem function.

// In Day15

fun solvePart1(): Int =
    instructions.sumOf { it.hash() }

Summing the hash of each instruction via sumOf gives us the answer to part 1.

Star earned! Onward!

⭐ Day 15, Part 2

The puzzle text can be found here.

Part 2 will require some thought in terms of our data structure. Originally, I went with a List<List<Lens>> where Lens was a data class I’d created. However, I changed my mind and rewrote it as we have it below because I didn’t like having to search for the label on the lens within a list.

Instead, we will rely on a property of Kotlin’s default MutableMap implementation which preserves insertion order. The documentation for mutableMapOf() states this explicitly - “The returned map preserves the entry iteration order." This will help us keep the lenses in order and if we replace of the values associated with a label the order will remain the same. This greatly simplifies the code vs. what I had before. If the documentation hadn’t explicitly called out that the insertion order is maintained, I would have used linkedMapOf instead (which is what mutableMapOf effectively does).

// In Day15
fun solvePart2(): Int {
    val boxes = List<MutableMap<String, Int>>(256) { mutableMapOf() }

    instructions.forEach { instruction ->
        if (instruction.endsWith("-")) {
            val label = instruction.substringBefore('-')
            boxes[label.hash()].remove(label)
        } else {
            val label = instruction.substringBefore('=')
            boxes[label.hash()][label] = instruction.substringAfter("=").toInt()
        }
    }

    return boxes.withIndex().sumOf { (boxNumber, lenses) ->
        lenses.values.withIndex().sumOf { (lensNumber, lens) ->
            (boxNumber + 1) * (lensNumber + 1) * lens
        }
    }
}

We start out by defining 256 boxes as a List which contains a MutableMap<String,Int> (where the String key is the lens label and the Int value is the lens focal length). We initialize each member of the List via the lambda function, specifying that we want an empty mutable map.

Next, we loop through each of the instructions and figure out which kind we have. If we have a - instruction, we remove the label from the correct box, which we calculate by calling the hash function we wrote for part 1.

If we have an = instruction, either replace or set the label into the appropriate box. (Thanks to Charles Flynn on the Kotlin Slack for pointing out that I had this way more complicated than it needed to be!).

All that’s left is to sum all of the lens values. We do this via a set of nested withIndex functions. The withIndex function is handy for when we have a sequence of items we want along with their index. Since Kotlin doesn’t have a sumWithIndex function, this is the next best thing. Especially with Kotlin’s support for destructuring, we can pick out the boxNumber, lenses, lensNumber, and lens from the IndexedValue objects that the withIndex function returns.

Star earned! See you tomorrow!

Further Reading

  1. Index of All Solutions - All posts and solutions for 2023, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 15
  4. Advent of Code - Come join in and do these challenges yourself!