diff --git a/src/main/kotlin/no/rodland/advent_2023/Day22.kt b/src/main/kotlin/no/rodland/advent_2023/Day22.kt index e9b603b..3bb0b3e 100644 --- a/src/main/kotlin/no/rodland/advent_2023/Day22.kt +++ b/src/main/kotlin/no/rodland/advent_2023/Day22.kt @@ -6,26 +6,93 @@ import no.rodland.advent.Pos3D // template generated: 22/12/2023 // Fredrik Rødland 2023 -class Day22(val input: List) : Day> { +// https://todd.ginsberg.com/post/advent-of-code/2023/day22/ + +class Day22(val input: List) : Day> { private val parsed = input.parse() - override fun partOne(): Long { - return 2 + override fun partOne(): Int { + return parsed.size - parsed.structurallySignificant().size + } + + override fun partTwo(): Int { + return parsed.structurallySignificant().sumOf { it.topple().size - 1 } + } + + private fun Brick.topple(): Set = buildSet { + add(this@topple) + val untoppled = (parsed - this).toMutableSet() + do { + val willFall = untoppled + .filter { it.supportedBy.isNotEmpty() } + .filter { it.supportedBy.all { brick -> brick in this } } + .also { + untoppled.removeAll(it) + addAll(it) + } + } while (willFall.isNotEmpty()) } - override fun partTwo(): Long { - return 2 + private fun List.structurallySignificant(): List = filter { brick -> brick.supporting.any { it.supportedBy.size == 1 } } + private fun List.settle(): List = buildList { + this@settle.forEach { brick -> + var current = brick + do { + var settled = false + val supporters = filter { below -> below.canSupport(current) } + if (supporters.isEmpty() && !current.onGround()) { + val restingPlace = filter { it.z.last < current.z.first - 1 } + .maxOfOrNull { it.z.last }?.let { it + 1 } ?: GROUND + current = current.fall(restingPlace) + } else { + settled = true + supporters.forEach { below -> below.supports(current) } + add(current) + } + } while (!settled) + } + } + + data class Brick(val id: Int, val from: Pos3D, val to: Pos3D) : Comparable { + val x = from.x..to.x + val y = from.y..to.y + val z = from.z..to.z + + val supporting = mutableSetOf() + val supportedBy = mutableSetOf() + + + override fun compareTo(other: Brick): Int = + z.first - other.z.first + + fun supports(other: Brick) { + supporting += other + other.supportedBy += this + } + + fun canSupport(other: Brick): Boolean = x intersects other.x && y intersects other.y && z.last + 1 == other.z.first + fun onGround(): Boolean = z.first == GROUND + fun fall(restingPlace: Int): Brick = + copy( + from = Pos3D(from.x, from.y, restingPlace), + to = Pos3D(to.x, to.y, (restingPlace + (z.last - z.first))) + ) + + private infix fun IntRange.intersects(other: IntRange): Boolean = first <= other.last && last >= other.first } - data class Brick(val from: Pos3D, val to: Pos3D) override fun List.parse(): List { - return map { line -> + return mapIndexed { idx, line -> val (from, to) = line.split('~') - Brick(Pos3D(from), Pos3D(to)) - } + Brick(idx, Pos3D(from), Pos3D(to)) + }.sorted().settle() } override val day = "22".toInt() + + companion object { + const val GROUND = 1 + } } diff --git a/src/test/kotlin/no/rodland/advent_2023/Day22Test.kt b/src/test/kotlin/no/rodland/advent_2023/Day22Test.kt index 36bb2fc..6ad6718 100644 --- a/src/test/kotlin/no/rodland/advent_2023/Day22Test.kt +++ b/src/test/kotlin/no/rodland/advent_2023/Day22Test.kt @@ -15,10 +15,10 @@ internal class Day22Test { private val data22 = "2023/input_22.txt".readFile() private val test22 = "2023/input_22_test.txt".readFile() - private val resultTestOne = 2L - private val resultTestTwo = 2L - private val resultOne = 2L - private val resultTwo = 2L + private val resultTestOne = 5 + private val resultTestTwo = 7 + private val resultOne = 497 + private val resultTwo = 67468 val test = defaultTestSuiteParseOnInit( Day22(data22), @@ -29,6 +29,7 @@ internal class Day22Test { resultTwo, { Day22(data22) }, { Day22(test22) }, + numInitLive = 2, ) @Nested