Coverage Summary for Class: Day22p2Kt (day22p2)
Class |
Method, %
|
Line, %
|
Day22p2Kt |
100%
(3/3)
|
100%
(8/8)
|
Day22p2Kt$toSnapshot$1 |
100%
(1/1)
|
100%
(1/1)
|
Day22p2Kt$toSnapshot$2 |
100%
(1/1)
|
100%
(1/1)
|
Total |
100%
(5/5)
|
100%
(10/10)
|
package day22p2
fun String.sumOfOtherBricksToFallInChainReactions(): Int =
toSnapshot().settle().sumOfOtherBricksToFallInChainReactions()
fun String.numberOfBricksThatCanBeDisintegrated(): Int = toSnapshot().settle().numberOfBricksThatCanBeDisintegrated()
fun String.toSnapshot(): Snapshot = Snapshot(
this
.lineSequence()
.map { line -> line.split("~").map { it.split(",").map { n -> n.toInt() } } }
.mapIndexed { index, parts ->
Brick(index + 1, parts[0][0], parts[0][1], parts[0][2], parts[1][0], parts[1][1], parts[1][2])
}
.associateBy { it.id }
)
data class Snapshot(val bricks: Map<BrickId, Brick>) {
constructor(vararg bricks: Brick) : this(bricks.associateBy { it.id })
val maxX: Int = bricks.values.maxOf { it.end.x }
val maxY: Int = bricks.values.maxOf { it.end.y }
val maxZ: Int = bricks.values.maxOf { it.end.z }
fun settle(): Snapshot {
val bricksByMinZ = bricks.values.asSequence().sortedBy { it.start.z }.toMutableList()
val topDownXYView: MutableMap<Coordinate2D, Pair<Int, Brick>?> =
(0..maxX).flatMap { x -> (0..maxY).asSequence().map { y -> Coordinate2D(x, y) to null } }
.toMap(mutableMapOf())
val settledBricks = mutableListOf<Brick>()
while (bricksByMinZ.isNotEmpty()) {
val current = bricksByMinZ.removeFirst()
val supporterCandidates = current.xyCoordinates().mapNotNull { topDownXYView[it] }
val (supporterZ, supporters) = supporterCandidates.maxOfOrNull { it.first }?.let { max ->
max to supporterCandidates.asSequence().filter { it.first == max }.map { it.second.id }.toSet()
} ?: (0 to emptySet())
val updated = current.copy(
start = current.start.copy(z = supporterZ + 1),
end = current.end.copy(z = supporterZ + 1 + current.end.z - current.start.z),
supporters = supporters
)
settledBricks.add(updated)
updated.xyCoordinates().forEach { xy -> topDownXYView[xy] = updated.end.z to updated }
}
return Snapshot(settledBricks.associateBy { it.id })
}
fun bricksThatCanBeDisintegrated(): Set<BrickId> {
val result = bricks.keys.toMutableSet()
bricks.values.forEach { brick ->
if (brick.supporters.size == 1) {
result.remove(brick.supporters.single())
}
}
return result
}
fun numberOfBricksThatCanBeDisintegrated(): Int = bricksThatCanBeDisintegrated().size
fun sumOfOtherBricksToFallInChainReactions(): Int {
val chainReactionStarters =
bricks.values.asSequence().filter { it.supporters.size == 1 }.map { it.supporters.single() }.toSet()
return chainReactionStarters.sumOf { numOfOtherBricksToFallInChainReaction(it) }
}
private fun numOfOtherBricksToFallInChainReaction(id: BrickId): Int {
val supporterToSupportedsMap: Map<BrickId, Set<BrickId>> = bricks.values
.flatMap { supported -> supported.supporters.map { it to supported.id } }
.groupingBy { it.first }
.fold(emptySet()) { set, pair -> set + pair.second }
val supportedToSupportersMap: Map<BrickId, MutableSet<BrickId>> = supporterToSupportedsMap.asSequence()
.flatMap { (supporter, supporteds) -> supporteds.map { it to supporter } }
.groupingBy { it.first }
.fold(emptySet<BrickId>()) { set, pair -> set + pair.second }
.mapValues { it.value.toMutableSet() }
val disintegratedBricks = mutableListOf(id)
var disintegratedCount = 0
while (disintegratedBricks.isNotEmpty()) {
val current = disintegratedBricks.removeFirst()
supporterToSupportedsMap[current]?.forEach { supported ->
val supporters = supportedToSupportersMap.getValue(supported)
supporters.remove(current)
if (supporters.isEmpty()) {
disintegratedBricks.add(supported)
disintegratedCount++
}
}
}
return disintegratedCount
}
}
data class BrickId(val id: Int)
data class Brick(
val id: BrickId,
val start: Coordinate3D,
val end: Coordinate3D,
val supporters: Set<BrickId> = emptySet()
) {
constructor(id: Int, sx: Int, sy: Int, sz: Int, ex: Int, ey: Int, ez: Int, vararg supporters: Int) : this(
BrickId(id),
Coordinate3D(sx, sy, sz),
Coordinate3D(ex, ey, ez),
supporters.asSequence().map { BrickId(it) }.toSet()
)
init {
require(start.x <= end.x)
require(start.y <= end.y)
require(start.z <= end.z)
require(0 <= start.x)
require(0 <= start.y)
require(1 <= start.z)
}
fun xyCoordinates(): Set<Coordinate2D> =
(start.x..end.x).flatMap { x -> (start.y..end.y).map { y -> Coordinate2D(x, y) } }.toSet()
}
data class Coordinate2D(val x: Int, val y: Int)
data class Coordinate3D(val x: Int, val y: Int, val z: Int)