diff --git a/src/Utils.kt b/src/Utils.kt index 23dfa16..82e2236 100644 --- a/src/Utils.kt +++ b/src/Utils.kt @@ -11,6 +11,7 @@ private fun openFile(day: Int, name: String) = File("src/year%4d/day%02d".format */ fun readInput(day: Int, name: String) = openFile(day, name).readLines() +fun readInputAsString(day: Int, name: String) = openFile(day, name).readText() fun readInputAsInts(day: Int, name: String) = readInput(day, name).map { it.toInt() } fun readInputAsCommaSeparatedInts(day: Int, name: String) = openFile(day, name) .readText() diff --git a/src/year2021/day19/Day19.kt b/src/year2021/day19/Day19.kt new file mode 100644 index 0000000..e1d6391 --- /dev/null +++ b/src/year2021/day19/Day19.kt @@ -0,0 +1,131 @@ +package year2021.day19 + +import readInputAsString +import java.time.Instant +import java.util.* +import kotlin.math.absoluteValue +import kotlin.math.sin +import kotlin.math.cos +import kotlin.math.sqrt + +data class Vector(val x: Int, val y: Int, val z: Int) { + fun magnitude(): Double = sqrt((x * x + y * y + z * z).toDouble()) + fun translate(by: Vector): Vector = this - by + fun rotate(u: Vector, v: Vector, w: Vector): Vector = + Vector( + u.x * x + v.x * y + w.x * z, + u.y * x + v.y * y + w.y * z, + u.z * x + v.z * y + w.z * z, + ) + + operator fun plus(v: Vector): Vector = Vector(x + v.x, y + v.y, z + v.z) + operator fun minus(v: Vector): Vector = Vector(x - v.x, y - v.y, z - v.z) + operator fun unaryMinus(): Vector = Vector(-x, -y, -z) + operator fun times(c: Int): Vector = Vector(c * x, c * y, c * z) +} + +data class Scanner(val beacons: Set) + +// region parsing +fun String.toVector() = + this.split(",").map(String::toInt).let { (x, y, z) -> + Vector(x, y, z) + } + +fun String.toScanner() = + Scanner(this.split("\n").drop(1).map(String::toVector).toSet()) +// endregion + +// region constants +val TRIGONOMETRY: Set> = + listOf(0, 90, 180, 270) + .map { Math.toRadians(it.toDouble()) } + .map { sin(it).toInt() to cos(it).toInt() } + .toSet() + +val ROTATIONS: Set> = + TRIGONOMETRY + .flatMap { alpha -> TRIGONOMETRY.flatMap { beta -> TRIGONOMETRY.map { gamma -> Triple(alpha, beta, gamma) } } } + .map { (a, b, g) -> + val (sinA, cosA) = a + val (sinB, cosB) = b + val (sinG, cosG) = g + + Triple( + Vector(cosA * cosB, sinA * cosB, -sinB), + Vector(cosA * sinB * sinG - sinA * cosG, sinA * sinB * sinG + cosA * cosG, cosB * sinG), + Vector(cosA * sinB * cosG + sinA * sinG, sinA * sinB * cosG - cosA * sinG, cosB * cosG) + ) + } + .toSet() +// endregion + +fun checkOverlap(beacons: Set, scanner: Scanner): Pair>? { + ROTATIONS.forEach { (u, v, w) -> + val rotatedBeacons = scanner.beacons.map { it.rotate(u, v, w) } + + beacons.forEach { anchor -> // anchor from already located beacons + val translateWithRegardsToAnchor = beacons.map { it.translate(anchor) }.toSet() + rotatedBeacons.forEach { rotatedAnchor -> + val translateWithRegardsToRotatedAnchor = rotatedBeacons.map { it.translate(rotatedAnchor) }.toSet() + + val intersect = translateWithRegardsToAnchor.intersect(translateWithRegardsToRotatedAnchor) + if (intersect.size >= 12) { + val scannerPosition = rotatedAnchor - anchor + val translatedBeacons = rotatedBeacons.map { it - rotatedAnchor + anchor } + return scannerPosition to translatedBeacons + } + } + } + } + + return null +} + +fun locate(scanners: List): Pair, Set> { + val beacons = mutableSetOf() + val scannerPositions = mutableSetOf() + + // add first scanner that is used as anchor + beacons.addAll(scanners.first().beacons) + + val remainingScanners = scanners.drop(1).toMutableList() + while (remainingScanners.isNotEmpty()) { + for (i in remainingScanners.indices.reversed()) { + val (scannerPosition, translatedBeacons) = checkOverlap(beacons, remainingScanners[i]) ?: continue + + beacons.addAll(translatedBeacons) + scannerPositions.add(scannerPosition) + remainingScanners.removeAt(i) + println("Done: ${scanners.size - remainingScanners.size}/${scanners.size}") + } + } + + return scannerPositions to beacons +} + +fun part1(input: Set): Int = input.size + +fun part2(input: Set): Int = + input.maxOf { a -> + input.maxOf { b -> + (a.x - b.x).absoluteValue + (a.y - b.y).absoluteValue + (a.z - b.z).absoluteValue + } + } + +fun main() { + val sample = + readInputAsString(19, "sample").split("\n\n").map(String::toScanner) + val input = + readInputAsString(19, "input").split("\n\n").map(String::toScanner) + + println(Date.from(Instant.now())) + val (sampleScanners, sampleBeacons) = locate(sample) + check(part1(sampleBeacons) == 79) + check(part2(sampleScanners) == 3621) + + val (inputScanners, inputBeacons) = locate(input) + println(part1(inputBeacons)) + println(part2(inputScanners)) + println(Date.from(Instant.now())) +} diff --git a/src/year2021/day19/sample.txt b/src/year2021/day19/sample.txt new file mode 100644 index 0000000..b596cc4 --- /dev/null +++ b/src/year2021/day19/sample.txt @@ -0,0 +1,136 @@ +--- scanner 0 --- +404,-588,-901 +528,-643,409 +-838,591,734 +390,-675,-793 +-537,-823,-458 +-485,-357,347 +-345,-311,381 +-661,-816,-575 +-876,649,763 +-618,-824,-621 +553,345,-567 +474,580,667 +-447,-329,318 +-584,868,-557 +544,-627,-890 +564,392,-477 +455,729,728 +-892,524,684 +-689,845,-530 +423,-701,434 +7,-33,-71 +630,319,-379 +443,580,662 +-789,900,-551 +459,-707,401 + +--- scanner 1 --- +686,422,578 +605,423,415 +515,917,-361 +-336,658,858 +95,138,22 +-476,619,847 +-340,-569,-846 +567,-361,727 +-460,603,-452 +669,-402,600 +729,430,532 +-500,-761,534 +-322,571,750 +-466,-666,-811 +-429,-592,574 +-355,545,-477 +703,-491,-529 +-328,-685,520 +413,935,-424 +-391,539,-444 +586,-435,557 +-364,-763,-893 +807,-499,-711 +755,-354,-619 +553,889,-390 + +--- scanner 2 --- +649,640,665 +682,-795,504 +-784,533,-524 +-644,584,-595 +-588,-843,648 +-30,6,44 +-674,560,763 +500,723,-460 +609,671,-379 +-555,-800,653 +-675,-892,-343 +697,-426,-610 +578,704,681 +493,664,-388 +-671,-858,530 +-667,343,800 +571,-461,-707 +-138,-166,112 +-889,563,-600 +646,-828,498 +640,759,510 +-630,509,768 +-681,-892,-333 +673,-379,-804 +-742,-814,-386 +577,-820,562 + +--- scanner 3 --- +-589,542,597 +605,-692,669 +-500,565,-823 +-660,373,557 +-458,-679,-417 +-488,449,543 +-626,468,-788 +338,-750,-386 +528,-832,-391 +562,-778,733 +-938,-730,414 +543,643,-506 +-524,371,-870 +407,773,750 +-104,29,83 +378,-903,-323 +-778,-728,485 +426,699,580 +-438,-605,-362 +-469,-447,-387 +509,732,623 +647,635,-688 +-868,-804,481 +614,-800,639 +595,780,-596 + +--- scanner 4 --- +727,592,562 +-293,-554,779 +441,611,-461 +-714,465,-776 +-743,427,-804 +-660,-479,-426 +832,-632,460 +927,-485,-438 +408,393,-506 +466,436,-512 +110,16,151 +-258,-428,682 +-393,719,612 +-211,-452,876 +808,-476,-593 +-575,615,604 +-485,667,467 +-680,325,-822 +-627,-443,-432 +872,-547,-609 +833,512,582 +807,604,487 +839,-516,451 +891,-625,532 +-652,-548,-490 +30,-46,-14 \ No newline at end of file