Skip to Content

Advent of Code 2018 - Day 14, in Kotlin

Kotlin solutions to parts 1 and 2 of Advent of Code 2018, Day 14: 'Chocolate Charts'

Posted on

I’m a bit late posting this because I’ve been moving and have been extra busy.

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

Problem Input

We are given a number, and we need it as a String, an Int, and a List<Int> eventually. Let’s do that here, and we can refer to them in parts 1 and 2.

class Day14(stoppingPoint: String) {

    private val stopInt = stoppingPoint.toInt()
    private val stopList = stoppingPoint.map { it.toString().toInt() }.toList()

}

Turning a String into a List<Int> where the Int is a digit looks complicated, but String.toInt() and Char.toInt() mean different things. If you skip one of the toString() calls, you’ll get character values, not the digit it represents! Also, we can’t just turn our Int into a List<Int> with our asDigits() function below, because inputs might start with zero, and we would mis-calculate them.

Day 14, Part 1

The puzzle text can be found here.

The problem is pretty limiting in terms of approaches we can take. We could write this as a sequence, but it would be easier for part 2 if we pass in a function to tell us when to stop. Essentially, we will keep generating recipes and ask the stopCondition function if we should stop. When it tells us to stop, we return the entire history we’ve generated so far.

private fun recipes(stopCondition: (List<Int>) -> Boolean): List<Int> {
    val history = mutableListOf(3, 7)
    var elf1 = 0
    var elf2 = 1
    var stop = false

    while (!stop) {
        val nextValue = history[elf1] + history[elf2]
        nextValue.asDigits().forEach {
            if (!stop) {
                history.add(it)
                stop = stopCondition(history)
            }
        }
        elf1 = (elf1 + history[elf1] + 1) % history.size
        elf2 = (elf2 + history[elf2] + 1) % history.size
    }
    return history
}

The part that got me for a while is that we have to check stopCondition twice! Why? If the elves create a great recipe, they produce two digits and not just one. So we have to check the stop condition for each history item added, not just the first one.

We also define this extension function to bust up an Int into a List<Int>

private fun Int.asDigits(): List<Int> =
    this.toString().map { it.toString().toInt() }

And with part 1, our stop condition is just based on the length:

fun solvePart1(): String =
    recipes { it.size == stopInt + 10 }.takeLast(10).joinToString("")

We generate recipes until we have 10 more than the stopping point we have is, and then we just stringify the last 10 elements.

Star earned! Onward!

Day 14, Part 2

The puzzle text can be found here.

See? I told you the higher-order function was the way to go. Defining a new function for part two solves the problem without altering our logic any more:

fun solvePart2(): Int =
    recipes { it.endsWith(stopList) }.size - stopList.size

We’ve got this handy extension function to quickly test that one List ends with another List. I found this to be fast enough to make part 2 not take too long.

private fun List<Int>.endsWith(other: List<Int>): Boolean =
    if (this.size < other.size) false
    else this.slice(this.size - other.size until this.size) == other

Star earned!

Further Reading

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