DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 5: Sunny with a Chance of Asteroids

Collapse
 
jbristow profile image
Jon Bristow

Initial kotlin solution (I'm going to clean this up a LOT, but in the interest of sharing what I learn, I promise not to pave this post over.)

import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.some
import java.nio.file.Files
import java.nio.file.Paths

object Day05 {
    sealed class Mode {
        object Immediate : Mode()
        object Position : Mode()
    }

    fun Char.toMode(): Mode {
        return when (this) {
            '0' -> Mode.Position
            '1' -> Mode.Immediate
            else -> throw Error("Bad mode $this")
        }
    }

    fun parseInstruction(instruction: String): Instruction {
        return Day05.Instruction(
            opcode = instruction.takeLast(2).toInt(),
            paramModeInput = instruction.take(
                kotlin.math.max(
                    0,
                    instruction.count() - 2
                )
            ).map { it.toMode() }.reversed()
        )
    }

    class Instruction(val opcode: Int, paramModeInput: List<Mode>) {
        val paramModes: Array<Mode> = arrayOf(Mode.Position, Mode.Position, Mode.Position, Mode.Position)

        init {
            paramModeInput.forEachIndexed { i, it ->
                paramModes[i] = it
            }
        }

        override fun toString(): String {
            return "Instruction[$opcode, ${paramModes.contentToString()}]"
        }
    }

    fun handle(pointer: Int, input: Int, code: Array<String>): Option<Int> {
        val instr = parseInstruction(code[pointer])
        val inputs = code.drop(pointer + 1).take(3)
        val params = inputs
            .zip(instr.paramModes).map { (value, mode) ->
                when (mode) {
                    Mode.Immediate -> value.toInt()
                    Mode.Position -> {
                        code.getOrNull(value.toInt())?.toInt() ?: -1000000
                    }
                }
            }
        return when (instr.opcode) {
            1 -> {
                code[inputs[2].toInt()] = (params[0] + params[1]).toString()
                (pointer + 4).some()
            }
            2 -> {
                code[inputs[2].toInt()] = (params[0] * params[1]).toString()
                (pointer + 4).some()
            }
            3 -> {
                code[inputs[0].toInt()] = input.toString()
                (pointer + 2).some()
            }
            4 -> {
                println("output:${params[0]}")
                (pointer + 2).some()
            }
            5 -> {
                when (params[0]) {
                    0 -> pointer + 3
                    else -> params[1]
                }.some()
            }
            6 -> {
                when (params[0]) {
                    0 -> params[1]
                    else -> pointer + 3
                }.some()
            }
            7 -> {
                code[inputs[2].toInt()] = when {
                    params[0] < params[1] -> "1"
                    else -> "0"
                }
                (pointer + 4).some()
            }
            8 -> {
                code[inputs[2].toInt()] = when {
                    params[0] == params[1] -> "1"
                    else -> "0"
                }
                (pointer + 4).some()
            }
            99 -> Option.empty<Int>()
            else -> throw Error("Unknown opcode: ${instr.opcode}")
        }
    }

    tailrec fun step(
        code: Array<String>,
        input: Int,
        instructionPointer: Option<Int> = 0.some()
    ) {
        return when (instructionPointer) {
            is None -> Unit
            is Some<Int> -> {
                val nextInstruction = handle(instructionPointer.t, input, code)
                step(code, input, nextInstruction)
            }
        }
    }

    const val FILENAME = "src/main/resources/day05.txt"
}

fun main() {
    // Part 01
    Day05.step(
        code = Files.readString(Paths.get(Day05.FILENAME)).trim().split(",").toTypedArray(),
        input = 1
    )

    // Part 02
    Day05.step(
        code = Files.readString(Paths.get(Day05.FILENAME)).trim().split(",").toTypedArray(),
        input = 5
    )
}

Collapse
 
jbristow profile image
Jon Bristow • Edited

Ok, as promised, here's the code I wanted to write, but I was halfway done before I saw it, and I wanted the answer so bad I didn't give myself the time to rewrite.

EDIT 2: Oops, all monads.

import arrow.core.Either
import arrow.core.Option
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.left
import arrow.core.right
import arrow.core.some
import java.nio.file.Files
import java.nio.file.Paths

sealed class Mode {
    object Immediate : Mode()
    object Position : Mode()
}

sealed class Instruction {
    abstract val opcodeFormat: String
    abstract fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int>

    open fun findInputs(code: Array<Int>, pointer: Int) =
        code.drop(pointer + 1)
            .take(3)
            .zip(opcodeFormat.format(code[pointer] / 100).map {
                when (it) {
                    '0' -> Mode.Position
                    '1' -> Mode.Immediate
                    else -> throw Error("Bad mode $it")
                }
            }.reversed())
            .map { (it, mode) ->
                when (mode) {
                    Mode.Position -> code[it]
                    Mode.Immediate -> it
                }
            }

    sealed class ThreeParameterInstruction : Instruction() {
        override val opcodeFormat = "1%02d"

        class Add : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = params[0] + params[1]
                return (pointer + 4).right()
            }
        }

        class Multiply : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = params[0] * params[1]
                return (pointer + 4).right()
            }
        }

        class LessThan : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = when {
                    params[0] < params[1] -> 1
                    else -> 0
                }
                return (pointer + 4).right()
            }
        }

        class Equal : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = when {
                    params[0] == params[1] -> 1
                    else -> 0
                }
                return (pointer + 4).right()
            }
        }
    }

    sealed class TwoParameterInstruction : Instruction() {
        override val opcodeFormat = "%02d"

        class JumpIfTrue : TwoParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>) =
                when (params[0]) {
                    0 -> pointer + 3
                    else -> params[1]
                }.right()
        }

        class JumpIfFalse : TwoParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>) =
                when (params[0]) {
                    0 -> params[1]
                    else -> pointer + 3
                }.right()
        }
    }

    class SetFromInput(private val input: Int) : Instruction() {
        override val opcodeFormat = "1"
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
            code[params[0]] = input
            return (pointer + 2).right()
        }
    }

    class Output : Instruction() {
        override val opcodeFormat = "0"
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
            println("output: ${params[0]}")
            return (pointer + 2).right()
        }
    }

    object End : Instruction() {
        override val opcodeFormat: String
            get() = throw Error("No opcode format for End instructions.")

        override fun findInputs(code: Array<Int>, pointer: Int) = emptyList<Int>()
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> =
            Option.empty<String>().left()
    }
}

object Day05 {

    private fun parseInstruction(instruction: String, input: Int) =
        when (instruction.takeLast(2).toInt()) {
            1 -> Instruction.ThreeParameterInstruction.Add().right()
            2 -> Instruction.ThreeParameterInstruction.Multiply().right()
            3 -> Instruction.SetFromInput(input).right()
            4 -> Instruction.Output().right()
            5 -> Instruction.TwoParameterInstruction.JumpIfTrue().right()
            6 -> Instruction.TwoParameterInstruction.JumpIfFalse().right()
            7 -> Instruction.ThreeParameterInstruction.LessThan().right()
            8 -> Instruction.ThreeParameterInstruction.Equal().right()
            99 -> Instruction.End.right()
            else -> "Problem parsing instruction $instruction".some().left()
        }

    private fun handleCodePoint(pointer: Int, input: Int, code: Array<Int>) =
        parseInstruction(code[pointer].toString(), input).flatMap { instr ->
            instr.execute(pointer, code, instr.findInputs(code, pointer))
        }

    tailrec fun step(
        code: Array<Int>,
        input: Int,
        instructionPointer: Either<Option<String>, Int> = Either.right(0)
    ): String = when (instructionPointer) {
        is Either.Left<Option<String>> -> instructionPointer.a.getOrElse { "Program terminated successfully." }
        is Either.Right<Int> -> {
            val nextInstruction = handleCodePoint(instructionPointer.b, input, code)
            step(code, input, nextInstruction)
        }
    }

    const val FILENAME = "src/main/resources/day05.txt"
}

fun main() {
    val problemInput = Files.readAllLines(Paths.get(Day05.FILENAME))
        .first()
        .split(",")
        .map { it.toInt() }

    // Part 01
    println("Part 01")
    Day05.step(code = problemInput.toTypedArray(), input = 1)

    // Part 02
    println("\nPart 02")
    Day05.step(code = problemInput.toTypedArray(), input = 5)

    println("\nDay 02")
    val day02Code = Files.readAllLines(Paths.get("src/main/resources/day02.txt")).first()
        .split(",")
        .map { it.toInt() }.toTypedArray()
    Day05.step(code = day02Code, input = 5)
    println(day02Code.take(10))
}
Collapse
 
ballpointcarrot profile image
Christopher Kruse

Definitely can say this implementation has a lot of class. 🤣

Thread Thread
 
jbristow profile image
Jon Bristow

Yeah, I wish there was a more elegant way of doing discriminated unions in Kotlin. I'm used to the lightweight kinds/types of Haskell. Arrow-KT lets you do basic stuff pretty nicely, but it gets ugly the more you try to be full functional style only.

I'm also disappointed in the type erasure.