~macaptain/advent-of-code

1d20d3e6ac4c9082badcd14af90b8c9f00569c5a — Michael Captain 1 year, 7 months ago 0ebe656
Complete 2019 Day 17
A src/main/advent2019/Day17.kt => src/main/advent2019/Day17.kt +253 -0
@@ 0,0 1,253 @@
package advent2019

import common.Point

fun asciiOutput(output: List<Int>): List<String> {
    val cameraWidth = output.indexOf(10)
    return output.filterNot { it == 10 }.chunked(cameraWidth).map { line ->
        line.map { asciiCode -> Char(asciiCode) }.joinToString("")
    }
}

fun scaffoldIntersections(camera: List<String>): Set<Point> {
    val intersections = mutableSetOf<Point>()
    camera.forEachIndexed { j, row ->
        row.forEachIndexed { i, c ->
            val up = camera.getOrNull(j - 1)?.getOrNull(i)
            val down = camera.getOrNull(j + 1)?.getOrNull(i)
            val left = camera.getOrNull(j)?.getOrNull(i - 1)
            val right = camera.getOrNull(j)?.getOrNull(i + 1)
            if (listOf(c, up, down, left, right).all { it == '#' }) intersections.add(Point(i, j))
        }
    }
    return intersections
}

fun alignmentParameters(camera: List<String>): Int {
    return scaffoldIntersections(camera).sumOf { it.x * it.y }
}

fun wakeVacuumRobotProgram(program: String): String {
    val xs = program.toMutableList()
    assert(xs[0] == '1')
    xs[0] = '2'
    return xs.joinToString("")
}

fun findRobot(camera: List<String>): Pair<Point, Direction> {
    camera.forEachIndexed { j, row ->
        row.forEachIndexed { i, c ->
            when (c) {
                '^' -> return Pair(Point(i, j), Direction.North)
                '>' -> return Pair(Point(i, j), Direction.East)
                '<' -> return Pair(Point(i, j), Direction.West)
                'v' -> return Pair(Point(i, j), Direction.South)
            }
        }
    }
    throw IllegalArgumentException("couldn't find robot in camera")
}

fun walkRobot(cameraMap: Map<Point, Char>, pos: Point, dir: Direction): List<Point> {
    val cameraMaxX = cameraMap.keys.maxOf { it.x }
    val cameraMaxY = cameraMap.keys.maxOf { it.y }
    val tileRay = when (dir) {
        Direction.North -> (pos.y - 1 downTo 0).map {
            Point(pos.x, it)
        }
        Direction.East -> (pos.x + 1..cameraMaxX).map {
            Point(it, pos.y)
        }
        Direction.South -> (pos.y + 1..cameraMaxY).map {
            Point(pos.x, it)
        }
        Direction.West -> (pos.x - 1 downTo 0).map {
            Point(it, pos.y)
        }
    }
    return tileRay.takeWhile { cameraMap[it] == '#' }
}

fun cleaningInstructions(camera: List<String>): List<String> {
    val instructions = mutableListOf<String>()
    val totalScaffoldTiles = camera.sumOf { row -> row.count { c -> c == '#' } }
    val seen = mutableSetOf<Point>()
    var (pos, dir) = findRobot(camera)
    val cameraMap = camera.mapIndexed { j, row ->
        row.mapIndexed { i, c ->
            Point(i, j) to c
        }
    }.flatten().toMap()
    while (seen.count() != totalScaffoldTiles) {
        val adjacentTiles = listOf(
            Point(pos.x - 1, pos.y),
            Point(pos.x + 1, pos.y),
            Point(pos.x, pos.y - 1),
            Point(pos.x, pos.y + 1),
        )
        val nextTile = adjacentTiles.first { cameraMap[it] == '#' && it !in seen }

        val (nextDir, turn) = when (dir) {
            Direction.North -> when (nextTile) {
                Point(pos.x - 1, pos.y) -> Pair(Direction.West, "L")
                Point(pos.x + 1, pos.y) -> Pair(Direction.East, "R")
                else -> throw IllegalArgumentException("can't turn to scaffold at $pos facing $dir")
            }
            Direction.East -> when (nextTile) {
                Point(pos.x, pos.y - 1) -> Pair(Direction.North, "L")
                Point(pos.x, pos.y + 1) -> Pair(Direction.South, "R")
                else -> throw IllegalArgumentException("can't turn to scaffold at $pos facing $dir")
            }
            Direction.South -> when (nextTile) {
                Point(pos.x - 1, pos.y) -> Pair(Direction.West, "R")
                Point(pos.x + 1, pos.y) -> Pair(Direction.East, "L")
                else -> throw IllegalArgumentException("can't turn to scaffold at $pos facing $dir")
            }
            Direction.West -> when (nextTile) {
                Point(pos.x, pos.y - 1) -> Pair(Direction.North, "R")
                Point(pos.x, pos.y + 1) -> Pair(Direction.South, "L")
                else -> throw IllegalArgumentException("can't turn to scaffold at $pos facing $dir")
            }
        }
        instructions.add(turn)
        dir = nextDir
        val tiles = walkRobot(cameraMap, pos, dir)
        instructions.add(tiles.count().toString())
        seen.addAll(tiles)
        pos = tiles.last()
    }
    return instructions
}

data class CompressedInstructions(
    val main: List<String>,
    val A: List<String>,
    val B: List<String>,
    val C: List<String>
) {
    fun compile(): List<String> {
        return main.flatMap {
            when (it) {
                "A" -> A
                "B" -> B
                "C" -> C
                else -> throw IllegalArgumentException("invalid char in routine $it")
            }
        }
    }

    private fun toCommaSeparatedAscii(xs: List<String>): List<Int> {
        return xs.flatMap { it.toCharArray().map { it.code } + ','.code }.dropLast(1) + '\n'.code
    }

    fun mainAscii() = toCommaSeparatedAscii(main)
    fun aAscii() = toCommaSeparatedAscii(A)
    fun bAscii() = toCommaSeparatedAscii(B)
    fun cAscii() = toCommaSeparatedAscii(C)

    fun isWithinMemoryLimit(): Boolean {
        return mainAscii().count() <= 21 &&
                aAscii().count() <= 21 &&
                bAscii().count() <= 21 &&
                cAscii().count() <= 21
    }
}

fun <T> replaceAllChunks(xs: List<T>, matchChunk: List<T>, replaceChunk: List<T>): List<T> {
    val replacement = xs.toMutableList()
    var windowed = replacement.windowed(matchChunk.count())
    while (matchChunk in windowed) {
        val index = windowed.indexOfFirst { it == matchChunk }
        (0 until matchChunk.count()).forEach { _ ->
            replacement.removeAt(index)
        }
        replacement.addAll(index, replaceChunk)
        windowed = replacement.windowed(matchChunk.count())
    }
    return replacement
}

fun compressInstructions(instructions: List<String>): CompressedInstructions {
    // brute force compression
    (6..20).forEach A@{ tryA ->
        val subroutineA = instructions.take(tryA)
        val replaceA = replaceAllChunks(instructions, subroutineA, listOf("A"))
        val initialAs = replaceA.takeWhile { it == "A" }.count()
        (2..20).forEach B@{ tryB ->
            val subroutineB = replaceA.drop(initialAs).take(tryB)
            if ("A" in subroutineB) return@B
            val replaceB = replaceAllChunks(replaceA, subroutineB, listOf("B"))
            val initialAsOrBs = replaceB.takeWhile { it == "A" || it == "B" }.count()
            (2..20).forEach C@{ tryC ->
                val subroutineC = replaceB.drop(initialAsOrBs).take(tryC)
                if ("A" in subroutineC || "B" in subroutineC) return@C
                val replaceC = replaceAllChunks(replaceB, subroutineC, listOf("C"))
                if (replaceC.toSet() != setOf("A", "B", "C")) return@C
                val ci = CompressedInstructions(replaceC, subroutineA, subroutineB, subroutineC)
                if (ci.compile() == instructions && ci.isWithinMemoryLimit()) return ci
            }
        }
    }
    throw IllegalArgumentException("couldn't compress instructions")
}

fun solve17a(program: String): Int {
    val computer = runProgram(program)
    val output = computer.outputs().map { it.toInt() }.toList()
    val camera = asciiOutput(output)
    return alignmentParameters(camera)
}

fun solve17b(program: String): Int {
    val robotProgram = wakeVacuumRobotProgram(program)
    val computer = runProgram(robotProgram)
    val output1 = computer.outputs().map { it.toInt() }.toList()
    val camera1 = asciiOutput(output1).dropLast(1)
    var prompt = asciiOutput(output1).last()
    assert(prompt == "Main:")
    val instructions = cleaningInstructions(camera1)
    val compressedInstructions = compressInstructions(instructions)

    val mainAscii = compressedInstructions.mainAscii()
    mainAscii.forEach { computer.input(it) }
    computer.run()
    val output2 = computer.outputs().map { it.toInt() }.toList()
    prompt = asciiOutput(output2).last()
    assert(prompt == "Function A:")

    val aAscii = compressedInstructions.aAscii()
    aAscii.forEach { computer.input(it) }
    computer.run()
    val output3 = computer.outputs().map { it.toInt() }.toList()
    prompt = asciiOutput(output3).last()
    assert(prompt == "Function B:")

    val bAscii = compressedInstructions.bAscii()
    bAscii.forEach { computer.input(it) }
    computer.run()
    val output4 = computer.outputs().map { it.toInt() }.toList()
    prompt = asciiOutput(output4).last()
    assert(prompt == "Function C:")

    val cAscii = compressedInstructions.cAscii()
    cAscii.forEach { computer.input(it) }
    computer.run()
    val output5 = computer.outputs().map { it.toInt() }.toList()
    prompt = asciiOutput(output5).last()
    assert(prompt == "Continuous video feed?")

    computer.input('n'.code)
    computer.input('\n'.code)
    computer.run()

    val output6 = computer.outputs().map { it.toInt() }.toList()
    val camera2 = asciiOutput(output6.drop(1).dropLast(1))

    return output6.last()
}

fun main() {
    val program = input("day17").first()
    println(solve17a(program)) // 4800
    println(solve17b(program)) // 982279
}
\ No newline at end of file

A src/main/resources/advent2019/day17_input.txt => src/main/resources/advent2019/day17_input.txt +1 -0
@@ 0,0 1,1 @@
1,330,331,332,109,3594,1102,1182,1,15,1102,1,1487,24,1001,0,0,570,1006,570,36,1001,571,0,0,1001,570,-1,570,1001,24,1,24,1106,0,18,1008,571,0,571,1001,15,1,15,1008,15,1487,570,1006,570,14,21102,1,58,0,1106,0,786,1006,332,62,99,21101,0,333,1,21102,73,1,0,1106,0,579,1102,1,0,572,1102,0,1,573,3,574,101,1,573,573,1007,574,65,570,1005,570,151,107,67,574,570,1005,570,151,1001,574,-64,574,1002,574,-1,574,1001,572,1,572,1007,572,11,570,1006,570,165,101,1182,572,127,1001,574,0,0,3,574,101,1,573,573,1008,574,10,570,1005,570,189,1008,574,44,570,1006,570,158,1105,1,81,21101,0,340,1,1106,0,177,21102,1,477,1,1106,0,177,21101,0,514,1,21101,0,176,0,1105,1,579,99,21102,184,1,0,1106,0,579,4,574,104,10,99,1007,573,22,570,1006,570,165,1001,572,0,1182,21102,375,1,1,21102,211,1,0,1106,0,579,21101,1182,11,1,21101,222,0,0,1106,0,979,21101,0,388,1,21102,233,1,0,1105,1,579,21101,1182,22,1,21102,244,1,0,1105,1,979,21102,401,1,1,21102,255,1,0,1106,0,579,21101,1182,33,1,21102,1,266,0,1105,1,979,21102,1,414,1,21101,0,277,0,1106,0,579,3,575,1008,575,89,570,1008,575,121,575,1,575,570,575,3,574,1008,574,10,570,1006,570,291,104,10,21101,1182,0,1,21101,0,313,0,1105,1,622,1005,575,327,1101,1,0,575,21102,327,1,0,1105,1,786,4,438,99,0,1,1,6,77,97,105,110,58,10,33,10,69,120,112,101,99,116,101,100,32,102,117,110,99,116,105,111,110,32,110,97,109,101,32,98,117,116,32,103,111,116,58,32,0,12,70,117,110,99,116,105,111,110,32,65,58,10,12,70,117,110,99,116,105,111,110,32,66,58,10,12,70,117,110,99,116,105,111,110,32,67,58,10,23,67,111,110,116,105,110,117,111,117,115,32,118,105,100,101,111,32,102,101,101,100,63,10,0,37,10,69,120,112,101,99,116,101,100,32,82,44,32,76,44,32,111,114,32,100,105,115,116,97,110,99,101,32,98,117,116,32,103,111,116,58,32,36,10,69,120,112,101,99,116,101,100,32,99,111,109,109,97,32,111,114,32,110,101,119,108,105,110,101,32,98,117,116,32,103,111,116,58,32,43,10,68,101,102,105,110,105,116,105,111,110,115,32,109,97,121,32,98,101,32,97,116,32,109,111,115,116,32,50,48,32,99,104,97,114,97,99,116,101,114,115,33,10,94,62,118,60,0,1,0,-1,-1,0,1,0,0,0,0,0,0,1,12,20,0,109,4,1202,-3,1,587,20101,0,0,-1,22101,1,-3,-3,21102,1,0,-2,2208,-2,-1,570,1005,570,617,2201,-3,-2,609,4,0,21201,-2,1,-2,1106,0,597,109,-4,2106,0,0,109,5,2101,0,-4,630,20101,0,0,-2,22101,1,-4,-4,21102,1,0,-3,2208,-3,-2,570,1005,570,781,2201,-4,-3,653,20102,1,0,-1,1208,-1,-4,570,1005,570,709,1208,-1,-5,570,1005,570,734,1207,-1,0,570,1005,570,759,1206,-1,774,1001,578,562,684,1,0,576,576,1001,578,566,692,1,0,577,577,21101,0,702,0,1106,0,786,21201,-1,-1,-1,1105,1,676,1001,578,1,578,1008,578,4,570,1006,570,724,1001,578,-4,578,21102,1,731,0,1105,1,786,1106,0,774,1001,578,-1,578,1008,578,-1,570,1006,570,749,1001,578,4,578,21101,0,756,0,1106,0,786,1105,1,774,21202,-1,-11,1,22101,1182,1,1,21101,0,774,0,1106,0,622,21201,-3,1,-3,1106,0,640,109,-5,2105,1,0,109,7,1005,575,802,21001,576,0,-6,20102,1,577,-5,1105,1,814,21101,0,0,-1,21101,0,0,-5,21102,1,0,-6,20208,-6,576,-2,208,-5,577,570,22002,570,-2,-2,21202,-5,49,-3,22201,-6,-3,-3,22101,1487,-3,-3,1202,-3,1,843,1005,0,863,21202,-2,42,-4,22101,46,-4,-4,1206,-2,924,21101,1,0,-1,1106,0,924,1205,-2,873,21102,35,1,-4,1105,1,924,1202,-3,1,878,1008,0,1,570,1006,570,916,1001,374,1,374,1202,-3,1,895,1102,1,2,0,1201,-3,0,902,1001,438,0,438,2202,-6,-5,570,1,570,374,570,1,570,438,438,1001,578,558,922,20101,0,0,-4,1006,575,959,204,-4,22101,1,-6,-6,1208,-6,49,570,1006,570,814,104,10,22101,1,-5,-5,1208,-5,43,570,1006,570,810,104,10,1206,-1,974,99,1206,-1,974,1102,1,1,575,21101,973,0,0,1106,0,786,99,109,-7,2106,0,0,109,6,21102,0,1,-4,21101,0,0,-3,203,-2,22101,1,-3,-3,21208,-2,82,-1,1205,-1,1030,21208,-2,76,-1,1205,-1,1037,21207,-2,48,-1,1205,-1,1124,22107,57,-2,-1,1205,-1,1124,21201,-2,-48,-2,1105,1,1041,21102,1,-4,-2,1105,1,1041,21102,1,-5,-2,21201,-4,1,-4,21207,-4,11,-1,1206,-1,1138,2201,-5,-4,1059,1202,-2,1,0,203,-2,22101,1,-3,-3,21207,-2,48,-1,1205,-1,1107,22107,57,-2,-1,1205,-1,1107,21201,-2,-48,-2,2201,-5,-4,1090,20102,10,0,-1,22201,-2,-1,-2,2201,-5,-4,1103,2102,1,-2,0,1105,1,1060,21208,-2,10,-1,1205,-1,1162,21208,-2,44,-1,1206,-1,1131,1105,1,989,21101,439,0,1,1105,1,1150,21101,477,0,1,1105,1,1150,21101,0,514,1,21101,0,1149,0,1105,1,579,99,21102,1,1157,0,1106,0,579,204,-2,104,10,99,21207,-3,22,-1,1206,-1,1138,1202,-5,1,1176,2102,1,-4,0,109,-6,2105,1,0,14,9,40,1,7,1,40,1,7,1,40,1,7,1,40,1,7,1,40,1,7,1,40,1,7,1,40,1,7,1,40,1,3,5,1,7,5,13,14,1,3,1,5,1,5,1,5,1,11,1,14,1,3,1,5,1,5,1,5,1,11,1,14,1,3,1,5,1,5,1,5,1,11,1,14,7,3,1,3,13,7,1,18,1,1,1,3,1,3,1,1,1,5,1,3,1,7,1,18,1,1,1,3,13,3,1,7,1,18,1,1,1,7,1,1,1,9,1,7,1,18,1,1,1,7,1,1,1,9,9,18,1,1,1,7,1,1,1,32,13,1,1,1,1,3,7,22,1,3,1,1,1,5,1,1,1,1,1,3,1,5,1,8,13,1,1,3,13,3,1,5,1,8,1,13,1,5,1,5,1,1,1,5,1,5,1,8,1,13,1,5,1,5,1,1,1,5,1,5,1,8,1,13,1,5,1,5,1,1,1,5,1,5,1,8,1,13,7,5,1,1,13,8,1,25,1,7,1,14,1,5,9,11,1,7,1,14,1,5,1,7,1,11,1,7,1,14,1,5,1,7,1,11,1,7,1,14,1,5,1,7,1,11,1,7,1,14,1,5,1,7,13,7,7,8,1,5,1,33,1,8,7,33,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,40,9,8

A src/test/advent2019/Day17KtTest.kt => src/test/advent2019/Day17KtTest.kt +82 -0
@@ 0,0 1,82 @@
package advent2019

import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*

internal class Day17KtTest {

    private val camera1 = listOf(
        "..#..........",
        "..#..........",
        "#######...###",
        "#.#...#...#.#",
        "#############",
        "..#...#...#..",
        "..#####...^.."
    )

    private val camera2 = listOf(
        "#######...#####",
        "#.....#...#...#",
        "#.....#...#...#",
        "......#...#...#",
        "......#...###.#",
        "......#.....#.#",
        "^########...#.#",
        "......#.#...#.#",
        "......#########",
        "........#...#..",
        "....#########..",
        "....#...#......",
        "....#...#......",
        "....#...#......",
        "....#####......"
    )

    private val expectedCleaningInstructions = listOf(
        "R",
        "8",
        "R",
        "8",
        "R",
        "4",
        "R",
        "4",
        "R",
        "8",
        "L",
        "6",
        "L",
        "2",
        "R",
        "4",
        "R",
        "4",
        "R",
        "8",
        "R",
        "8",
        "R",
        "8",
        "L",
        "6",
        "L",
        "2"
    )

    @Test
    fun alignmentParameters() {
        assertEquals(76, alignmentParameters(camera1))
    }

    @Test
    fun cleaningInstructions() {
        assertEquals(expectedCleaningInstructions, cleaningInstructions(camera2))
    }

    @Test
    fun compressInstructions() {
        assertEquals(expectedCleaningInstructions, compressInstructions(expectedCleaningInstructions).compile())
    }
}
\ No newline at end of file