diff --git a/src/year2021/day22/Day22.kt b/src/year2021/day22/Day22.kt index e8e6073..ed80a25 100644 --- a/src/year2021/day22/Day22.kt +++ b/src/year2021/day22/Day22.kt @@ -4,12 +4,21 @@ import product import readInput // region Global extensions -fun IntRange.intersect(other: IntRange): IntRange = maxOf(first, other.first)..minOf(last, other.last) -fun IntRange.size(): Long = if (isEmpty()) 0L else last - first + 1L +fun IntRange.intersect(other: IntRange): IntRange = + maxOf(first, other.first)..minOf(last, other.last) + +val IntRange.size: Long + get() = if (isEmpty()) 0L else last - first + 1L + fun IntRange.splitBy(other: IntRange): Sequence = - sequenceOf(first until other.first, other, other.last + 1..last) + sequenceOf( + first until other.first, other, other.last + 1..last + ) .map { it.intersect(this) } .filterNot(IntRange::isEmpty) + +val IntRange.Companion.MAX_RANGE: IntRange + get() = Int.MIN_VALUE..Int.MAX_VALUE // endregion Global extensions // region State @@ -24,20 +33,12 @@ fun String.toState(): State = when (this) { } // endregion State -// region Point -data class Point(val x: Int, val y: Int, val z: Int) { - fun within(xRange: IntRange, yRange: IntRange, zRange: IntRange): Boolean = - x in xRange && y in yRange && z in zRange -} -// endregion Point - // region Cuboid -data class Cuboid(val xRange: IntRange, val yRange: IntRange, val zRange: IntRange) { - val allPoints: Iterable - get() = xRange.flatMap { x -> yRange.flatMap { y -> zRange.map { z -> Point(x, y, z) } } } - +data class Cuboid( + val xRange: IntRange, val yRange: IntRange, val zRange: IntRange +) { val size: Long - get() = xRange.size() * yRange.size() * zRange.size() + get() = xRange.size * yRange.size * zRange.size fun intersect(other: Cuboid): Cuboid = Cuboid( xRange = xRange.intersect(other.xRange), @@ -45,7 +46,13 @@ data class Cuboid(val xRange: IntRange, val yRange: IntRange, val zRange: IntRan zRange = zRange.intersect(other.zRange), ) - private fun isEmpty(): Boolean = xRange.isEmpty() || yRange.isEmpty() || zRange.isEmpty() + private fun intersectedBy(other: Cuboid): Boolean = + (xRange.first in other.xRange + && yRange.first in other.yRange + && zRange.first in other.zRange) + + fun isEmpty(): Boolean = + xRange.isEmpty() || yRange.isEmpty() || zRange.isEmpty() operator fun minus(other: Cuboid): Set { val overlap = this.intersect(other) @@ -55,33 +62,26 @@ data class Cuboid(val xRange: IntRange, val yRange: IntRange, val zRange: IntRan } else if (overlap == this) { emptySet() } else { - buildSet { - for ((xs, ys, zs) in product( - xRange.splitBy(overlap.xRange), - yRange.splitBy(overlap.yRange), - zRange.splitBy(overlap.zRange), - )) { - if ( - xs.first !in overlap.xRange - || ys.first !in overlap.yRange - || zs.first !in overlap.zRange - ) { - add(Cuboid(xs, ys, zs)) - } - } - } + product( + xRange.splitBy(overlap.xRange), + yRange.splitBy(overlap.yRange), + zRange.splitBy(overlap.zRange), + ) + .map { (xs, ys, zs) -> Cuboid(xs, ys, zs) } + .filter { !it.intersectedBy(overlap) } + .toSet() } } + + companion object { + val MAX_CUBOID = + Cuboid(IntRange.MAX_RANGE, IntRange.MAX_RANGE, IntRange.MAX_RANGE) + } } // endregion Cuboid // region Step -data class Step(val state: State, val cuboid: Cuboid) { - val allPoints: Iterable - get() = cuboid.allPoints - - fun crop(cuboid: Cuboid): Step = this.copy(cuboid = this.cuboid.intersect(cuboid)) -} +data class Step(val state: State, val cuboid: Cuboid) fun String.toStep(): Step { val (state, regions) = this.split(" ") @@ -95,23 +95,21 @@ fun String.toStep(): Step { } // endregion Step -fun part1(input: List): Int = input.fold(mutableSetOf()) { turnedOn, step -> - val croppedStep = step.crop(Cuboid(-50..50, -50..50, -50..50)) - when (step.state) { - State.On -> turnedOn.addAll(croppedStep.allPoints) - State.Off -> turnedOn.removeAll(croppedStep.allPoints) - } - - turnedOn -}.size - // region Reactor -data class Reactor(val cuboids: List = emptyList()) { +data class Reactor( + val interestingCuboid: Cuboid = Cuboid.MAX_CUBOID, + val cuboids: List = emptyList() +) { fun execute(step: Step): Reactor { + val affectedCuboid = interestingCuboid.intersect(step.cuboid) + if (affectedCuboid.isEmpty()) { + return this + } + // remove current one from turned on - val withoutStep = cuboids.flatMapTo(mutableListOf()) { it - step.cuboid } + val withoutStep = cuboids.flatMapTo(mutableListOf()) { it - affectedCuboid } if (step.state == State.On) { - withoutStep.add(step.cuboid) + withoutStep.add(affectedCuboid) } return copy(cuboids = withoutStep) @@ -119,17 +117,24 @@ data class Reactor(val cuboids: List = emptyList()) { } // endregion Reactor -fun part2(input: List): Long = input.fold(Reactor()) { reactor, step -> - reactor.execute(step) -}.cuboids.sumOf { it.size } +val INIT_RANGE = -50..50 +fun part1(input: List): Long = + input.fold(Reactor(Cuboid(INIT_RANGE, INIT_RANGE, INIT_RANGE))) { reactor, step -> + reactor.execute(step) + }.cuboids.sumOf { it.size } + +fun part2(input: List): Long = + input.fold(Reactor()) { reactor, step -> + reactor.execute(step) + }.cuboids.sumOf { it.size } fun main() { val smallerSample = readInput(22, "smaller_sample").map(String::toStep) val sample = readInput(22, "sample").map(String::toStep) val input = readInput(22, "input").map(String::toStep) - check(part1(smallerSample) == 39) - check(part1(sample) == 590784) + check(part1(smallerSample) == 39L) + check(part1(sample) == 590784L) println(part1(input)) val rebootSample = readInput(22, "reboot_sample").map(String::toStep)