~macaptain/advent-of-code

2f225ec145de5fd279153ae155382f116ddbd323 — Michael Captain 1 year, 7 months ago 21976ab
Complete 2019 Day 18
A src/main/advent2019/Day18.kt => src/main/advent2019/Day18.kt +165 -0
@@ 0,0 1,165 @@
package advent2019

import common.Point

data class PointWithKeys(val p: Point, val keys: Set<Char>)
data class RobotsWithKeys(
    val p1: Point,
    val p2: Point,
    val p3: Point,
    val p4: Point,
    val keys: Set<Char>,
    val currentRobot: Int
)

fun neighbours(map: Map<Point, Char>, p: PointWithKeys): Set<PointWithKeys> {
    val x = p.p.x
    val y = p.p.y
    val adjacent = setOf(
        Point(x - 1, y),
        Point(x + 1, y),
        Point(x, y - 1),
        Point(x, y + 1)
    ).filter {
        (map[it] == '.') ||
                (map[it]?.isLetter() == true && map[it]!!.isLowerCase()) ||
                (map[it]?.isLetter() == true && map[it]!!.isUpperCase() && map[it]!!.lowercase().first() in p.keys) ||
                (map[it] == '@')
    }.map {
        if (map[it]?.isLetter() == true && map[it]!!.isLowerCase())
            PointWithKeys(it, p.keys.plus(map[it]!!))
        else
            PointWithKeys(it, p.keys)
    }.toSet()
    return adjacent
}

fun neighbours(map: Map<Point, Char>, p: RobotsWithKeys): Set<RobotsWithKeys> {
    // Only one robot moves at a time until a key is found. Then we queue as a neighbour all movements with any of the
    // robots moving.
    return when (p.currentRobot) {
        1 -> neighbours(map, PointWithKeys(p.p1, p.keys)).flatMap {
            if (!p.keys.containsAll(it.keys))
                (1..4).map { n -> RobotsWithKeys(it.p, p.p2, p.p3, p.p4, it.keys, n) }
            else listOf(
                RobotsWithKeys(it.p, p.p2, p.p3, p.p4, it.keys, 1)
            )
        }
        2 -> neighbours(map, PointWithKeys(p.p2, p.keys)).flatMap {
            if (!p.keys.containsAll(it.keys)) {
                (1..4).map { n -> RobotsWithKeys(p.p1, it.p, p.p3, p.p4, it.keys, n) }
            } else listOf(
                RobotsWithKeys(p.p1, it.p, p.p3, p.p4, it.keys, 2)
            )
        }
        3 -> neighbours(map, PointWithKeys(p.p3, p.keys)).flatMap {
            if (!p.keys.containsAll(it.keys)) {
                (1..4).map { n -> RobotsWithKeys(p.p1, p.p2, it.p, p.p4, it.keys, n) }
            } else listOf(
                RobotsWithKeys(p.p1, p.p2, it.p, p.p4, it.keys, 3)
            )
        }
        4 -> neighbours(map, PointWithKeys(p.p4, p.keys)).flatMap {
            if (!p.keys.containsAll(it.keys)) {
                (1..4).map { n -> RobotsWithKeys(p.p1, p.p2, p.p3, it.p, it.keys, n) }
            } else listOf(
                RobotsWithKeys(p.p1, p.p2, p.p3, it.p, it.keys, 4)
            )
        }
        else -> throw IllegalArgumentException("invalid robot ${p.currentRobot}")
    }.toSet()
}

fun solve18a(lines: List<String>): Int {
    val map = lines.flatMapIndexed { j, row ->
        row.mapIndexed { i, c ->
            Point(i, j) to c
        }
    }.toMap()

    val start = map.filterValues { it == '@' }.keys.first()
    val startPoint = PointWithKeys(start, setOf())
    val allKeys = map.filterValues { it.isLetter() && it.isLowerCase() }.values.toSet()
    val frontier = ArrayDeque<PointWithKeys>()
    frontier.add(startPoint)
    val cameFrom = mutableMapOf<PointWithKeys, PointWithKeys?>(startPoint to null)
    while (frontier.isNotEmpty()) {
        val current = frontier.removeFirst()
        if (current.keys == allKeys) {
            return generateSequence(cameFrom[current]) { cameFrom[it] }.count()
        }
        neighbours(map, current).forEach { next ->
            if (next !in cameFrom.keys) {
                cameFrom[next] = current
                frontier.add(next)
            }
        }
    }
    throw IllegalArgumentException("couldn't find a path through all keys")
}

fun updateMap(lines: List<String>): List<String> {
    val newLines = lines.map { it.toMutableList() }.toMutableList()
    lines.forEachIndexed { j, row ->
        row.forEachIndexed { i, c ->
            if (c == '@') {
                newLines[j - 1][i - 1] = '@'
                newLines[j - 1][i + 1] = '@'
                newLines[j + 1][i - 1] = '@'
                newLines[j + 1][i + 1] = '@'
                newLines[j][i - 1] = '#'
                newLines[j][i + 1] = '#'
                newLines[j - 1][i] = '#'
                newLines[j + 1][i] = '#'
                newLines[j][i] = '#'
            }
        }
    }
    return newLines.map { it.joinToString("") }
}

fun solve18b(updatedLines: List<String>): Int {
    val map = updatedLines.flatMapIndexed { j, row ->
        row.mapIndexed { i, c ->
            Point(i, j) to c
        }
    }.toMap()
    val startRobots = map.filterValues { it == '@' }.keys.toList()
    check(startRobots.count() == 4)
    val startPoints = listOf(
        RobotsWithKeys(startRobots[0], startRobots[1], startRobots[2], startRobots[3], setOf(), 1),
        RobotsWithKeys(startRobots[0], startRobots[1], startRobots[2], startRobots[3], setOf(), 2),
        RobotsWithKeys(startRobots[0], startRobots[1], startRobots[2], startRobots[3], setOf(), 3),
        RobotsWithKeys(startRobots[0], startRobots[1], startRobots[2], startRobots[3], setOf(), 4)
    )
    val allKeyPoints = map.filterValues { it.isLetter() && it.isLowerCase() }
    val allKeys = allKeyPoints.values.toSet()

    val frontier = ArrayDeque<RobotsWithKeys>()
    frontier.addAll(startPoints)
    val cameFrom = mutableMapOf<RobotsWithKeys, RobotsWithKeys?>(
        startPoints[0] to null,
        startPoints[1] to null,
        startPoints[2] to null,
        startPoints[3] to null
    )
    while (frontier.isNotEmpty()) {
        val current = frontier.removeFirst()
        if (current.keys == allKeys) {
            return generateSequence(cameFrom[current]) { cameFrom[it] }.count()
        }
        neighbours(map, current).forEach { next ->
            if (next !in cameFrom.keys) {
                cameFrom[next] = current
                frontier.add(next)
            }
        }
    }
    throw IllegalArgumentException("couldn't find a path through all keys")
}

fun main() {
    val lines = input("day18")
    println(solve18a(lines)) // 4590
    println(solve18b(updateMap(lines))) // 2086
}

A src/main/resources/advent2019/day18_input.txt => src/main/resources/advent2019/day18_input.txt +81 -0
@@ 0,0 1,81 @@
#################################################################################
#l....#.........#.....#.....#...........#...........#.......#...#...............#
#.#.###.#####.#.#####.#.###.#.###.#####.#.#.#########.#####.#.#.#.#####.#.#######
#.#.....#.#...#i......#.#...#.Z.#.....#.#.#e......A...#...#.#.#..q#...#.#.#.....#
#.#####.#.#.###########.#.###.#######.###.#########.#####.#.#.#####.#.#.###.###.#
#.#...#.#.#.....#...#...#.....#.....#...#...#.....#.......#.#...#...#.#.#.....#.#
#.#.#.#.#.#####.#.#.#.#########.###.###.#.###.###.#######.#.#.###.###.#.#.#####F#
#.#.#.#.......#...#.#.#.#.......#.....#.#.#...#.#...#.....#.#.#...#.#...#u..#...#
#.#.#.#############.#.#.#.###########.#.###.###.###.###.###.#.#.###.###.###.#.#.#
#.#.#...............#.#.#.....#...#...#.#...#.....#...#.#.#...#.#.........#.#.#.#
#.#.###############.#.#.#####.#.#.###.#.#.###.#######.#.#.#####.#####.#####.#.###
#.#...#.......#.#...#...#...#...#...#.#.#...#.#...#...#.#.N...#.....#...#...#...#
#####.#.#####.#.#.#####.###.#####.#.#.#.###.#.#.#.#.###.#.#.#######.###.#.#####.#
#.....#.#.....#.#...#.......#...#.#.#.#.#...#...#.#.#...#.#...#...#...#.#.#.#...#
#.#####.#.#####T###.#.#######.#.###.#.#.#.###.###.#.###.#.###.#.#.###.###.#.#.#.#
#.......#.#.........#...#.....#.....#.#.#...#.#...#...#.#..x#...#.#...#...#.#.#.#
#.#######.###########.###.###########.#.###.###.#.###.#.#########.#.###.###.#.#.#
#.#...#...#.......#...#...#.......#...#d#.#.#...#...#.#...#.......#.........#.#.#
#.###.#.###.#####.#.###.###.#####.#.###.#.#.#.#####.#.###.#.###.#######.#####J#.#
#.....#g..#.#...#.#...#.#.#...#...#...#.#.#...#...#.#.#.....#.S.#.....#.#.....#.#
#####.###.#.###.#.#.###.#.#.#.#.#####.#.#.#####.###.#.###.#####.#.###.###.#######
#.......#.......#.#.#...#.#.#.#...#...#.#...#.......#...#.#...#.#...#...#.......#
#############.###.#.#####.#.#.###.#.###.###.#.#########.###.#######.###.#.#####.#
#.........#...#...#.....#.#.#.#.#.#.#...#...#.#...#...#...#.........#...#.#.#...#
#.#######.###.#.#######.#.#.#.#.#.#.#.#B#.###.#.#.#.#.###.###########.###.#.#.#.#
#.#.....#...#.#.......#.#.#.#.#...#.#.#.#.....#.#.#.#...#...........#.#...#.#.#.#
#.###.#.###.#######.#.#.#.#.#.#.###.###.#.#####.#.#.#.#.#########.#.#.#.###.#.###
#...#.#...#...#...#.#...#...#.#.#...#...#.#.....#...#.#.#.....#.#.#.#.#.....#...#
###.#.#.#####.#.#.#.#####.###.#.#.###.###.###########.#.###.#.#.#.#.#G#########.#
#...#.#.#...#...#.#...#...#...#.#.#.....#.#.........#.#.....#.#.#.#.#.....#.....#
#.###.#.#.#.#####.###.#####.#.###.#.###.#.#.#######.#.#######.#.#.#######.#.###.#
#.#...#...#.....#...#.......#.#...#.#...#.#.#...#.#.#.....#.#.#.#.......#.#...#.#
#.#########.#.#.###.###.#######.###.#.#.#.#.#.#.#.#.###.#.#.#.#.#######.#.#.#.###
#.#.......#.#.#.#.#.#...#.......#...#.#.#.#...#.#.#...#.#.#.#.....#.#...#.#.#...#
#.#.#####.###.#.#.#.#####.###########.#.#.#.###.#.###.#.#.#.#####.#.#.###.#.###P#
#.#...#.#.....#.#.#...#...#...........#.#.#...#.#...#.#.#.#.......#.#.....#.#...#
#.###.#.#######.#.###.#.#########.#####.#.#####.#.#.#.###.#.#######.#########.#.#
#...#.#...#.....#...#...#.......#...#...#...#...#.#.#.#...#...#...#...#.......#.#
###.#.#.###.#####.#.#####.#####.###.#.#####.#.###.###.#.#####.#.#.#.#.###.#####.#
#w....#...........#.......#.........#.........#.........#.......#...#.....#.....#
#######################################.@.#######################################
#...........#...K...#.......#.......#.....#.....#.........#.......#.............#
#.#######.###.#.#####.#####.#.###.###.#.#.#.#.###.#####.###.#.#.###.###########.#
#p......#.#...#.......#...#.#...#.#...#.#...#.........#.....#.#.#...#.........#.#
#######.#.#.###########.#.#.###.#.#.###.#.###################.###.###.#.###.###.#
#y....#.#...#.#.......#.#.#...#.#...#.#.#...#...#.#.........#.....#...#...#.#...#
#.#.###.#####.#.###.#.#.#.#.#.#.#####.#.###.#.#.#.#.#######.#.#####.#####.###.#.#
#.#.#...#...#...#...#.#h#...#.#...#...#.#.#.#.#.#.#.#...#...#.....#.#...#.#...#.#
#.#.#.###.#.#.###.#####.###.#####.#.###.#.#.#.#.#.#.#.#.###.#####.###.#.#.#.###.#
#.#.#.....#.#...#...#...#...#...#...#...#.#.#.#...#.#.#...#.....#.#...#...#.#...#
#M#.#######.###.###.#.###.###.#.#.###.###.#.#.###.#.#.###.#####.#.#.#####.#.#####
#.#.#.....#.#...#...#...#...#.#...#...#.#b..#...#.#...#z#.#.....#...#...#.#.....#
#.###.#.#.#.#####.#####.#####.#####.###.#.###.#.#######.#.#####.#####.###.#####.#
#...#.#.#.#.....#.....#.#...#.#.....#...#.#...#.........#.....#.#.#.........#...#
###.#.#.#.#####.###.#.#.#.#.#.#.#####.#.#.#####.#######.#####.#.#.#.#######.#.#.#
#...#.#.#.....#.....#.#...#...#...#...#.#.#...#.......#.....#.#.#...#...#...#.#.#
#.###O#.#####.#######.#######D###.#.###.#.#.#.###########.#.#.#.#####.#.#####H#.#
#.#...#.#.........#.....#...#...#.#...#.#...#.............#.#.#.......#.#...#.#.#
#.#.###.###########.#####.#.#####.###.#.###.#############.###.#########.#.#.#.#.#
#.#.#.#.#...#.......#.....#.......#...#.#...#.....#.......#...#.....#.....#...#k#
#.#.#.#.#.#.#.#######.#############.#####.###.###.#########.###.###.###########.#
#...#.#...#.#f#.....#.#.......#...#.....#.#...#.#.......#...#...#.#...#.....#.#.#
#.###.#####.#.#####.###.#####.#.#.#####.###.###.#######.#.#######.###.#.###.#.#.#
#.#...#..r..#.....#.....#.....#.#.......#...#.....#...#...#.........#...#...#.#.#
#.###.#V###.#####.#.#####.#####.#######.#.#######.#.#.###.#.#######.#####.###.#.#
#.#...#...#.....#...#...#...#...#.....#.#.#.....#.#.#.....#.....#.....#...#.....#
#.#.#####.#####.#####.#.###.###.#.#####.#.#.###.#.#.###########.#.###.#E###.#####
#m#.#.....#.#.....R...#..o#.#...#.#...#.#.#.#...#...#.........#.#.#.#.#.#.#...#.#
#.#.#.#####.#.###########.#.#.###.#.#.#.#.#.#.#######.#######.#.#.#.#.#.#.###.#.#
#...#.#.......#.....#...#.#.#.#.#...#.#.#...#.........#...#.#.#.#.#...#.#...#.#.#
#.###.#######.#.###.###.#.#.#.#.#.###.#.###############.#.#.#.#.#.#####.###.#.#.#
#.#...#.....#.#...#...#...#.#.#...#...#.#.........#.....#...#.I.#.#.Q.#...#.#...#
#.#.###.###.#####.###.#####.#.#.###.###.#.###.#####.###.###.#####.#.#.###.#.###.#
#.#v#...#.#.....#n..#.#...#.#.#...#.L...#...#.#.....#.#.#..j....#...#.#...#...#.#
#.#.#X###.#####.###.#.#.#.#.#.###.#########.#.#.#####.#.#.###########.#.###.#.#.#
#.#...#...Y.#.#.....#s#.#.#...#.#...#...#.#.#...#.....#.#...#.........#...#.#...#
#.#####.###.#.#######.#.#.#####.###.###.#.#.#####.###.#.#####.###########.#.#####
#.#.......#.#.....#.#.#.#.#.......#...#.#...#.....#.#.#.....#..a..........#.W...#
#.#######.#.###.#C#.#.#.#.#.###.#####.#.#.###.#####.#U#####.###################.#
#.........#c....#...#...#.....#.........#.....#t..........#.....................#
#################################################################################

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

import org.junit.jupiter.api.Test

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

internal class Day18KtTest {

    private val exampleA1 = listOf(
        "#########",
        "#b.A.@.a#",
        "#########"
    )

    private val exampleA2 = listOf(
        "########################",
        "#f.D.E.e.C.b.A.@.a.B.c.#",
        "######################.#",
        "#d.....................#",
        "########################"
    )

    private val exampleA3 = listOf(
        "########################",
        "#...............b.C.D.f#",
        "#.######################",
        "#.....@.a.B.c.d.A.e.F.g#",
        "########################"
    )

    private val exampleA4 = listOf(
        "#################",
        "#i.G..c...e..H.p#",
        "########.########",
        "#j.A..b...f..D.o#",
        "########@########",
        "#k.E..a...g..B.n#",
        "########.########",
        "#l.F..d...h..C.m#",
        "#################"
    )

    private val exampleA5 = listOf(
        "########################",
        "#@..............ac.GI.b#",
        "###d#e#f################",
        "###A#B#C################",
        "###g#h#i################",
        "########################"
    )

    private val exampleB1 = listOf(
        "#######",
        "#a.#Cd#",
        "##@#@##",
        "#######",
        "##@#@##",
        "#cB#Ab#",
        "#######"
    )

    private val exampleB2 = listOf(
        "###############",
        "#d.ABC.#.....a#",
        "######@#@######",
        "###############",
        "######@#@######",
        "#b.....#.....c#",
        "###############"
    )

    private val exampleB3 = listOf(
        "#############",
        "#DcBa.#.GhKl#",
        "#.###@#@#I###",
        "#e#d#####j#k#",
        "###C#@#@###J#",
        "#fEbA.#.FgHi#",
        "#############"
    )

    private val exampleB4 = listOf(
        "#############",
        "#g#f.D#..h#l#",
        "#F###e#E###.#",
        "#dCba@#@BcIJ#",
        "#############",
        "#nK.L@#@G...#",
        "#M###N#H###.#",
        "#o#m..#i#jk.#",
        "#############"
    )

    @Test
    fun solve18a() {
        assertEquals(8, solve18a(exampleA1))
        assertEquals(86, solve18a(exampleA2))
        assertEquals(132, solve18a(exampleA3))
        assertEquals(136, solve18a(exampleA4))
        assertEquals(81, solve18a(exampleA5))
    }

    @Test
    fun updateMap() {
        assertEquals(
            listOf(
                "@#@",
                "###",
                "@#@"
            ),
            updateMap(
                listOf(
                    "...",
                    ".@.",
                    "..."
                )
            )
        )

        assertEquals(
            listOf(
                "#######",
                "#a.#Cd#",
                "##@#@##",
                "#######",
                "##@#@##",
                "#cB#Ab#",
                "#######"
            ),
            updateMap(
                listOf(
                    "#######",
                    "#a.#Cd#",
                    "##...##",
                    "##.@.##",
                    "##...##",
                    "#cB#Ab#",
                    "#######"
                )
            ),
        )
    }

    @Test
    fun solve18b() {
        assertEquals(8, solve18b(exampleB1))
        assertEquals(24, solve18b(exampleB2))
        assertEquals(32, solve18b(exampleB3))
        assertEquals(72, solve18b(exampleB4))
    }
}
\ No newline at end of file