Skip to Content

Advent of Code 2018 - Day 21, in Kotlin

Kotlin solutions to parts 1 and 2 of Advent of Code 2018, Day 21: 'Chronal Conversion'

Posted on

Today we get to manually go through some more Elf Code, and reuse a bunch of code form Day 19.

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

Problem Input

Identical to Day 19, so we’ll parse it the same way.

⭐ Day 21, Part 1

The puzzle text can be found here.

There sure is a lot of overlap in the code we’ll need for this one and the code we wrote in Day 19! On order to make things easier, we’ll pull Day 19’s Operations and Instructions objects out into a file called ElfCode.kt. We’ll also rename them to ElfCodeOperations and ElfCodeInstruction respectively in order not to collide too much with other code we’ve written (or will write).

Let’s talk about a solution. We could keep poking values into register 0 and rerunning the program, and write some kind of watchdog process to kill it off if we think it is in an endless loop. I don’t think that’s what the puzzle author had in mind. Instead, we’re going to look through the input (the Elf Code) and figure out what instructions touch register 0. In mine, it is instruction 28:

eqrr 4 0 5 

Which essentialy says register[5] = if(register[4] == register[0]) 1 else 0. Therefore, if we can execute instructions until we get to #28, and then take the value of register 4 (in this case), we would have matched register 0 and ended. Because I know what part 2 is, we’ll write this to emit a sequence of Int, which represents register 4 in our case. We will call these magicRegister (4) and magicInstruction (28) and specify them in the constructor:

// In Day21

class Day21(rawInput: List<String>, private val magicRegister: Int = 4, private val magicInstruction: Int = 28) {

    private val instructionPointer: Int = rawInput.first().split(" ")[1].toInt()
    private val instructions: List<ElfCodeInstruction> = ElfCodeInstruction.of(rawInput.drop(1))

    private fun execute(instructions: List<ElfCodeInstruction>, ipBind: Int): Sequence<Int> = sequence {
        var registers = IntArray(6)
        var ip = registers[ipBind]
        val seen = LinkedHashSet<Int>()
        while (ip in (0 until instructions.size)) {
            registers[ipBind] = ip
            val thisInstruction = instructions[ip]
            registers = ElfCodeOperations[thisInstruction.name].invoke(registers, thisInstruction)
            ip = registers[ipBind] + 1
            if(ip == magicInstruction) {
                if(registers[magicRegister] in seen) {
                    yield(seen.last())
                    return@sequence
                }
                seen += registers[magicRegister]
                yield(registers[magicRegister])
            }
        }
    }
}

The execute function should look pretty similar to the one in Day 19 except for detecting our magic instruction number (28 in my case) and maintaining the sequence. The end condition is when we’ve gone through a cycle and the numbers start to repeat. We’ll do that by saving each number we yield off into a LinkedHashSet, which will remember them in the order they are inserted.

And we can solve now, by taking the first element of the sequence:

fun solvePart1(): Int =
    execute(instructions, instructionPointer).first()

Star earned! Onward!

⭐ Day 21, Part 2

The puzzle text can be found here.

Since we wrote part 1 as a sequence, we can just let this run until we get the last answer. Given that this is effectively interpreted code, you can probably get this a lot faster by rewriting the ElfCode into Kotlin (or anything else, really).

fun solvePart2(): Int =
    execute(instructions, instructionPointer).last()

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 21
  4. Advent of Code - Come join in and do these challenges yourself!