diff options
author | tchojnacki <tomaszchojnacki2001@gmail.com> | 2022-08-12 16:18:47 +0200 |
---|---|---|
committer | tchojnacki <tomaszchojnacki2001@gmail.com> | 2022-08-12 16:18:47 +0200 |
commit | 438250af060fbba14dc18e088e5e16dc5a7b808c (patch) | |
tree | 0e4a03ac8a418b0e40b85ba230bedb523accb998 | |
parent | 0f1e145b80813ae2331b7dac5ace0c589654ad2a (diff) | |
download | gleam_aoc2020-438250af060fbba14dc18e088e5e16dc5a7b808c.tar.gz gleam_aoc2020-438250af060fbba14dc18e088e5e16dc5a7b808c.zip |
Finish day 19
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | aoc-2021-kotlin/README.md | 6 | ||||
-rw-r--r-- | aoc-2021-kotlin/src/Day19.kt | 234 | ||||
-rw-r--r-- | aoc-2021-kotlin/src/Utils.kt | 4 |
4 files changed, 241 insertions, 7 deletions
@@ -8,8 +8,8 @@ Repository storing my solutions to Advent of Code. See other people's solutions ## Years 📅 ### [2021](aoc-2021-kotlin)  - - + + [aoc]: https://adventofcode.com diff --git a/aoc-2021-kotlin/README.md b/aoc-2021-kotlin/README.md index c8c777a..e2188a4 100644 --- a/aoc-2021-kotlin/README.md +++ b/aoc-2021-kotlin/README.md @@ -1,7 +1,7 @@ # Advent of Code 2021 in Kotlin  - - + + Welcome to the Advent of Code Kotlin project created by [tchojnacki][github] using the [Advent of Code Kotlin Template][template] delivered by JetBrains. @@ -28,7 +28,7 @@ Welcome to the Advent of Code Kotlin project created by [tchojnacki][github] usi | Day 16: Packet Decoder | 🌟 | 🌟 | | Day 17: Trick Shot | 🌟 | 🌟 | | Day 18: Snailfish | 🌟 | 🌟 | -| Day 19: Beacon Scanner | | | +| Day 19: Beacon Scanner | 🌟 | 🌟 | | Day 20: Trench Map | | | | Day 21: Dirac Dice | | | | Day 22: Reactor Reboot | | | diff --git a/aoc-2021-kotlin/src/Day19.kt b/aoc-2021-kotlin/src/Day19.kt new file mode 100644 index 0000000..8bc403a --- /dev/null +++ b/aoc-2021-kotlin/src/Day19.kt @@ -0,0 +1,234 @@ +import kotlin.math.absoluteValue + +object Day19 { + private class Scanner private constructor(val beacons: Set<Pos3D>) { + companion object { + private const val THRESHOLD = 12 + + fun withCenteredBeacon() = Scanner(setOf(Pos3D(0, 0, 0))) + + fun parseScanners(lines: List<String>): List<Scanner> { + val strippedLines = lines.filter { it.isNotBlank() } + + val groupStartIndices = strippedLines + .asSequence() + .withIndex() + .filter { it.value.startsWith("---") } + .map { it.index } + .toList() + + return strippedLines + .withIndex() + .groupBy { + groupStartIndices.last { idx -> idx <= it.index } + } + .values + .map { entry -> + Scanner( + entry + .asSequence() + .filter { !it.value.startsWith("---") } + .map { Pos3D.fromString(it.value) } + .toSet() + ) + } + } + + private fun findOffset(from: Scanner, to: Scanner, axis: (Pos3D) -> Int) = + to.beacons + .flatMap { t -> from.beacons.map { f -> axis(t) - axis(f) } } + .toSet() + .find { offset -> + intersection( + from.beacons.map { axis(it) + offset }, + to.beacons.map(axis) + ).size >= THRESHOLD + } + + fun findTransform(from: Scanner, to: Scanner): Transform? { + if (!from.canOverlapWith(to)) return null + + val targetOffsets = uniquePairs(to.beacons) + .flatMap { (a, b) -> listOf(a - b, b - a) } + + val rotation = Transform.Rotation.allRotations.find { rotation -> + intersection( + targetOffsets, + uniquePairs(from.withTransform(rotation).beacons).map { (a, b) -> a - b } + ).count() >= THRESHOLD * (THRESHOLD - 1) / 2 + } ?: return null + + val rotatedScanner = from.withTransform(rotation) + + val translation = Transform.Translation( + Pos3D( + findOffset(rotatedScanner, to) { it.x }!!, + findOffset(rotatedScanner, to) { it.y }!!, + findOffset(rotatedScanner, to) { it.z }!! + ) + ) + + return rotation + translation + } + } + + private fun beaconOffsetIds() = uniquePairs(beacons).map { (a, b) -> + (b - a).let { + setOf( + it.x.absoluteValue, + it.y.absoluteValue, + it.z.absoluteValue + ) + } + } + + private fun canOverlapWith(other: Scanner) = + intersection(beaconOffsetIds(), other.beaconOffsetIds()) + .count() >= THRESHOLD * (THRESHOLD - 1) / 2 + + fun withTransform(transform: Transform) = Scanner(beacons.map { transform.apply(it) }.toSet()) + } + + private sealed class Transform { + companion object { + val identity get() = Composite(listOf()) + } + + abstract fun apply(pos: Pos3D): Pos3D + abstract fun inverted(): Transform + protected abstract val list: List<Transform> + abstract override fun toString(): String + + operator fun plus(other: Transform) = Composite(list + other.list) + + class Translation(private val offset: Pos3D) : Transform() { + override fun apply(pos: Pos3D) = pos + offset + override fun inverted() = Translation(-offset) + override val list get() = listOf(this) + override fun toString() = offset.let { (x, y, z) -> "T($x,$y,$z)" } + } + + class Rotation private constructor(private val index: Int) : Transform() { + companion object { + val allRotations = (0 until 24).map { Rotation(it) } + } + + override fun apply(pos: Pos3D) = pos.let { (x, y, z) -> + listOf( + Pos3D(x, y, z), Pos3D(x, z, -y), Pos3D(x, -y, -z), Pos3D(x, -z, y), + Pos3D(-x, -y, z), Pos3D(-x, z, y), Pos3D(-x, y, -z), Pos3D(-x, -z, -y), + Pos3D(y, z, x), Pos3D(y, x, -z), Pos3D(y, -z, -x), Pos3D(y, -x, z), + Pos3D(-y, -z, x), Pos3D(-y, x, z), Pos3D(-y, z, -x), Pos3D(-y, -x, -z), + Pos3D(z, x, y), Pos3D(z, y, -x), Pos3D(z, -x, -y), Pos3D(z, -y, x), + Pos3D(-z, -x, y), Pos3D(-z, y, x), Pos3D(-z, x, -y), Pos3D(-z, -y, -x) + ) + }[index] + + override fun inverted() = Pos3D(1, 2, 3).let { + allRotations.find { inverse -> + inverse.apply(this.apply(it)) == it + }!! + } + + override val list get() = listOf(this) + + override fun toString() = "R($index)" + } + + class Composite(private val queue: List<Transform>) : Transform() { + override fun apply(pos: Pos3D) = queue.fold(pos) { acc, transform -> transform.apply(acc) } + override fun inverted() = Composite(queue.reversed().map { it.inverted() }) + override val list get() = queue + override fun toString() = if (list.isEmpty()) "I" else "C(${queue.joinToString("->")})" + } + } + + private data class Pos3D(val x: Int, val y: Int, val z: Int) { + companion object { + fun fromString(string: String) = string + .split(",") + .map(String::toInt) + .let { Pos3D(it[0], it[1], it[2]) } + } + + operator fun unaryMinus() = Pos3D(-x, -y, -z) + + operator fun plus(other: Pos3D) = Pos3D(x + other.x, y + other.y, z + other.z) + + operator fun minus(other: Pos3D) = Pos3D(x - other.x, y - other.y, z - other.z) + } + + private class Graph(private val adjacencyList: List<Set<Edge>>) { + data class Edge(val destination: Int, val transform: Transform) + + private fun getEdge(from: Int, to: Int) = adjacencyList[from].find { it.destination == to } + + fun findTransformsToZero(): List<Transform> { + val predecessors = mutableMapOf(0 to 0) + val queue = mutableListOf(0) + + while (queue.isNotEmpty()) { + val current = queue.removeFirst() + for (edge in adjacencyList[current].filter { !predecessors.contains(it.destination) }) { + predecessors[edge.destination] = current + queue.add(edge.destination) + } + } + + return adjacencyList.indices.map { i -> + generateSequence(i) { if (it == 0) null else predecessors[it] } + .zipWithNext() + .map { (a, b) -> getEdge(a, b)!!.transform } + .fold(Transform.identity) { a, b -> a + b } + } + } + + override fun toString() = adjacencyList.withIndex().joinToString("\n") { "${it.index}: ${it.value}" } + } + + private fun <T> uniquePairs(iterable: Iterable<T>) = + iterable.withIndex().flatMap { + iterable.drop(it.index + 1).map { second -> it.value to second } + } + + private fun <T> intersection(first: Iterable<T>, second: Iterable<T>): List<T> { + val remaining = first.toMutableList() + return second.filter { remaining.remove(it) } + } + + fun bothParts(input: List<String>) = Scanner.parseScanners(input).let { scanners -> + val edges = uniquePairs(scanners.withIndex()) + .mapNotNull { (a, b) -> + Scanner.findTransform(a.value, b.value)?.let { a.index to Graph.Edge(b.index, it) } + } + .flatMap { listOf(it, it.second.destination to Graph.Edge(it.first, it.second.transform.inverted())) } + + val transforms = Graph(scanners.indices.map { i -> + edges.asSequence().filter { it.first == i }.map { it.second }.toSet() + }).findTransformsToZero() + + val firstPart = scanners + .zip(transforms) + .map { it.first.withTransform(it.second).beacons } + .reduce { a, b -> a + b }.size + + val scannerPositions = transforms.map { Scanner.withCenteredBeacon().withTransform(it).beacons.first() } + val secondPart = uniquePairs(scannerPositions).maxOfOrNull { + (it.second - it.first).let { (x, y, z) -> x.absoluteValue + y.absoluteValue + z.absoluteValue } + } + + return@let firstPart to secondPart + } +} + +fun main() { + val testInput = readInputAsLines("Day19_test") + val testOutput = Day19.bothParts(testInput) + check(testOutput.first == 79) + check(testOutput.second == 3621) + + val input = readInputAsLines("Day19") + val output = Day19.bothParts(input) + println(output.first) + println(output.second) +} diff --git a/aoc-2021-kotlin/src/Utils.kt b/aoc-2021-kotlin/src/Utils.kt index f0a420b..8fe3f75 100644 --- a/aoc-2021-kotlin/src/Utils.kt +++ b/aoc-2021-kotlin/src/Utils.kt @@ -1,8 +1,8 @@ import java.io.File -fun readInputAsLines(name: String): List<String> = File("src", "$name.txt").readLines() +fun readInputAsLines(name: String): List<String> = File("aoc-2021-kotlin/src", "$name.txt").readLines() -fun readInputAsString(name: String): String = File("src", "$name.txt").readText() +fun readInputAsString(name: String): String = File("aoc-2021-kotlin/src", "$name.txt").readText() fun readInputAsNumbers(name: String): List<Int> = readInputAsLines(name).map(String::toInt) |