aboutsummaryrefslogtreecommitdiff
path: root/aoc-2021-kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'aoc-2021-kotlin')
-rw-r--r--aoc-2021-kotlin/src/Day19.kt92
1 files changed, 62 insertions, 30 deletions
diff --git a/aoc-2021-kotlin/src/Day19.kt b/aoc-2021-kotlin/src/Day19.kt
index 8bc403a..bcdabac 100644
--- a/aoc-2021-kotlin/src/Day19.kt
+++ b/aoc-2021-kotlin/src/Day19.kt
@@ -3,9 +3,9 @@ import kotlin.math.absoluteValue
object Day19 {
private class Scanner private constructor(val beacons: Set<Pos3D>) {
companion object {
- private const val THRESHOLD = 12
+ private const val MIN_OVERLAP = 12
- fun withCenteredBeacon() = Scanner(setOf(Pos3D(0, 0, 0)))
+ fun withCenteredBeacon() = Scanner(setOf(Pos3D.zero))
fun parseScanners(lines: List<String>): List<Scanner> {
val strippedLines = lines.filter { it.isNotBlank() }
@@ -36,13 +36,13 @@ object Day19 {
private fun findOffset(from: Scanner, to: Scanner, axis: (Pos3D) -> Int) =
to.beacons
- .flatMap { t -> from.beacons.map { f -> axis(t) - axis(f) } }
+ .flatMap { t -> from.beacons.map { f -> axis(t - f) } }
.toSet()
.find { offset ->
intersection(
from.beacons.map { axis(it) + offset },
to.beacons.map(axis)
- ).size >= THRESHOLD
+ ).size >= MIN_OVERLAP
}
fun findTransform(from: Scanner, to: Scanner): Transform? {
@@ -55,7 +55,7 @@ object Day19 {
intersection(
targetOffsets,
uniquePairs(from.withTransform(rotation).beacons).map { (a, b) -> a - b }
- ).count() >= THRESHOLD * (THRESHOLD - 1) / 2
+ ).count() >= MIN_OVERLAP * (MIN_OVERLAP - 1) / 2
} ?: return null
val rotatedScanner = from.withTransform(rotation)
@@ -73,39 +73,37 @@ object Day19 {
}
private fun beaconOffsetIds() = uniquePairs(beacons).map { (a, b) ->
- (b - a).let {
- setOf(
- it.x.absoluteValue,
- it.y.absoluteValue,
- it.z.absoluteValue
- )
+ (b - a).let { (x, y, z) ->
+ sequenceOf(x, y, z).map { it.absoluteValue }.toSet()
}
}
private fun canOverlapWith(other: Scanner) =
intersection(beaconOffsetIds(), other.beaconOffsetIds())
- .count() >= THRESHOLD * (THRESHOLD - 1) / 2
+ .count() >= MIN_OVERLAP * (MIN_OVERLAP - 1) / 2
- fun withTransform(transform: Transform) = Scanner(beacons.map { transform.apply(it) }.toSet())
+ fun withTransform(transform: Transform) = Scanner(beacons.map { transform.applyTo(it) }.toSet())
}
private sealed class Transform {
companion object {
- val identity get() = Composite(listOf())
+ val identity: Transform = Composite.empty
}
- abstract fun apply(pos: Pos3D): Pos3D
+ protected val isIdentity get() = applyTo(Pos3D.unique) == Pos3D.unique
+
+ abstract fun applyTo(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)
+ operator fun plus(other: Transform) = Composite.wrap(this) + Composite.wrap(other)
class Translation(private val offset: Pos3D) : Transform() {
- override fun apply(pos: Pos3D) = pos + offset
+ override fun applyTo(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)" }
+
+ operator fun plus(other: Translation) = Translation(offset + other.offset)
}
class Rotation private constructor(private val index: Int) : Transform() {
@@ -113,7 +111,7 @@ object Day19 {
val allRotations = (0 until 24).map { Rotation(it) }
}
- override fun apply(pos: Pos3D) = pos.let { (x, y, z) ->
+ override fun applyTo(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),
@@ -124,27 +122,61 @@ object Day19 {
)
}[index]
- override fun inverted() = Pos3D(1, 2, 3).let {
- allRotations.find { inverse ->
- inverse.apply(this.apply(it)) == it
+ override fun inverted() = allRotations.find { (this + it).isIdentity }!!
+
+ override fun toString() = "R($index)"
+
+ operator fun plus(other: Rotation) = Pos3D.unique.let {
+ allRotations.find { combination ->
+ other.applyTo(this.applyTo(it)) == combination.applyTo(it)
}!!
}
+ }
- override val list get() = listOf(this)
+ class Composite private constructor(private val queue: List<Transform>) : Transform() {
+ companion object {
+ val empty = Composite(listOf())
- override fun toString() = "R($index)"
- }
+ fun wrap(transform: Transform) = when {
+ transform.isIdentity -> empty
+ transform is Composite -> transform
+ else -> Composite(listOf(transform))
+ }
+ }
- class Composite(private val queue: List<Transform>) : Transform() {
- override fun apply(pos: Pos3D) = queue.fold(pos) { acc, transform -> transform.apply(acc) }
+ override fun applyTo(pos: Pos3D) = queue.fold(pos) { acc, transform -> transform.applyTo(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("->")})"
+ override fun toString() = if (queue.isEmpty()) "I" else "C(${queue.joinToString("->")})"
+
+ operator fun plus(other: Composite): Transform {
+ val stack = queue.toMutableList()
+
+ other.queue.forEach {
+ if (stack.isEmpty()) {
+ stack.add(it)
+ return@forEach
+ }
+
+ val last = stack.removeLast()
+ when {
+ it is Rotation && last is Rotation -> stack.add(last + it)
+ it is Translation && last is Translation -> stack.add(last + it)
+ else -> stack.addAll(listOf(last, it))
+ }
+
+ while (stack.last().isIdentity) stack.removeLast()
+ }
+
+ return stack.singleOrNull() ?: Composite(stack)
+ }
}
}
private data class Pos3D(val x: Int, val y: Int, val z: Int) {
companion object {
+ val zero = Pos3D(0, 0, 0)
+ val unique = Pos3D(1, 2, 3)
+
fun fromString(string: String) = string
.split(",")
.map(String::toInt)