Skip to content

Commit c75d176

Browse files
committed
continue day22
1 parent b29a9b7 commit c75d176

File tree

2 files changed

+58
-131
lines changed
  • src
    • main/kotlin/tr/emreone/adventofcode/days
    • test/kotlin/tr/emreone/adventofcode/days

2 files changed

+58
-131
lines changed
Lines changed: 57 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,15 @@
11
package tr.emreone.adventofcode.days
22

33
import tr.emreone.kotlin_utils.automation.Day
4-
import tr.emreone.kotlin_utils.math.Point2D
54
import tr.emreone.kotlin_utils.math.Point3D
65
import java.util.*
6+
import kotlin.collections.ArrayDeque
77
import kotlin.math.max
88
import kotlin.math.min
99

1010
class Day22 : Day(22, 2023, "Sand Slabs") {
1111

12-
data class Brick(val id: Int, val a: Point3D, val b: Point3D) {
13-
private val xRange = rangeBy { it.x.toInt() }
14-
private val yRange = rangeBy { it.y.toInt() }
15-
private val zRange = rangeBy { it.z.toInt() }
16-
17-
private fun rangeBy(f: (Point3D) -> Int) = min(f(a), f(b))..max(f(a), f(b))
18-
19-
val xyPoints = xRange.flatMap { xi ->
20-
yRange.map { yi ->
21-
Point2D(xi.toLong(), yi.toLong())
22-
}
23-
}
24-
25-
val minZ
26-
get() = zRange.first
27-
val maxZ
28-
get() = zRange.last
29-
30-
fun fallBy(fallAmount: Int) = copy(
31-
a = Point3D(x = a.x, y = a.y, z = a.z - fallAmount),
32-
b = Point3D(x = b.x, y = b.y, z = b.z - fallAmount)
33-
)
34-
12+
data class Brick(val id: Int, var a: Point3D, var b: Point3D) {
3513
override fun toString() = "[$id] $a~$b"
3614

3715
fun overlapsWith(other: Brick): Boolean {
@@ -40,33 +18,6 @@ class Day22 : Day(22, 2023, "Sand Slabs") {
4018
}
4119
}
4220

43-
data class Snapshot(val bricks: Set<Brick>) {
44-
45-
fun makeBricksFall(): Snapshot {
46-
val tetris = BrickTetris(this)
47-
tetris.makeAllBricksFall(print = true)
48-
return Snapshot(tetris.bricks().toSet())
49-
}
50-
51-
// returns number of bricks that would fall if given brick were removed
52-
private fun countAffectedBricksAfterRemovalOf(brick: Brick): Int {
53-
val tetris = BrickTetris(this)
54-
tetris.remove(brick)
55-
tetris.makeAllBricksFall()
56-
return tetris.bricks().count { it !in bricks }
57-
}
58-
59-
fun findBrickRemovalCounts(): Map<Brick, Int> = bricks.associateWith { brick ->
60-
countAffectedBricksAfterRemovalOf(brick)
61-
}
62-
}
63-
64-
data class BrickFall(val oldBrick: Brick, val fallAmount: Int) {
65-
fun newBrick(): Brick {
66-
return oldBrick.fallBy(fallAmount)
67-
}
68-
}
69-
7021
private val bricks = inputAsList
7122
.mapIndexed { index, line ->
7223
val (left, right) = line.split("~")
@@ -76,105 +27,81 @@ class Day22 : Day(22, 2023, "Sand Slabs") {
7627

7728
Brick(index, Point3D(x1, y1, z1), Point3D(x2, y2, z2))
7829
}
79-
.toSet()
80-
81-
class BrickTetris(snapshot: Snapshot) {
82-
83-
private val bricksByXY: MutableMap<Point2D, SortedSet<Brick>> = snapshot.bricks
84-
.flatMap { brick -> brick.xyPoints.map { xy -> xy to brick } }
85-
.groupBy({ (xy, _) -> xy }, { (_, brick) -> brick })
86-
.mapValues { (_, bricks) -> bricks.toSortedSet(compareBy { it.minZ }) }
87-
.toMutableMap()
88-
89-
private val bricksByMinZ: SortedMap<Int, MutableList<Brick>> = snapshot.bricks
90-
.groupBy { it.minZ }
91-
.mapValues { (_, bricks) -> bricks.toMutableList() }
92-
.toSortedMap()
93-
94-
95-
private var lastCheckedMinZ = 1
96-
97-
fun bricks() = bricksByMinZ.values.asSequence().flatten()
30+
.sortedBy { it.a.z }
31+
.also {
32+
it.forEachIndexed { index, brick ->
33+
var maxZ = 1L
34+
for (i in 0 until index) {
35+
val other = it.elementAt(i)
36+
if (brick.overlapsWith(other)) {
37+
maxZ = max(maxZ, other.b.z + 1)
38+
}
39+
}
40+
brick.b = Point3D(brick.b.x, brick.b.y, brick.b.z - brick.a.z + maxZ)
41+
brick.a = Point3D(brick.a.x, brick.a.y, maxZ)
42+
}
43+
}
44+
.sortedBy { it.a.z }
9845

99-
private fun bricksAt(xy: Point2D) = bricksByXY[xy]!!
10046

101-
private fun bricksAt(minZ: Int) = bricksByMinZ[minZ]
47+
override fun part1(): Int {
48+
// https://www.youtube.com/watch?v=imz7uexX394
10249

103-
fun remove(brick: Brick) {
104-
brick.xyPoints.forEach { xy -> bricksAt(xy) -= brick }
50+
val supports = this.bricks.associate { it.id to mutableSetOf<Int>() }
51+
val supportedBy = this.bricks.associate { it.id to mutableSetOf<Int>() }
10552

106-
bricksAt(brick.minZ)?.let {
107-
it -= brick
53+
this.bricks.forEachIndexed { j, upperBrick ->
54+
this.bricks.subList(0, j).forEachIndexed { i, lowerBrick ->
55+
if (upperBrick.overlapsWith(lowerBrick) && upperBrick.a.z == lowerBrick.b.z + 1) {
56+
supports[i]!! += j
57+
supportedBy[j]!! += i
58+
}
10859
}
10960
}
11061

111-
private fun add(brick: Brick) {
112-
brick.xyPoints.forEach { xy -> bricksAt(xy) += brick }
113-
114-
bricksAt(brick.minZ)?.let {
115-
it += brick
116-
}
62+
return supports.values.count { set ->
63+
set.all { supportedBy[it]!!.size > 1 }
11764
}
65+
}
11866

119-
fun makeAllBricksFall(print: Boolean = false) {
120-
var counter = 0
121-
while (tryMakeNextBrickFall()) {
122-
counter++
67+
override fun part2(): Int {
68+
val supports = this.bricks.associate { it.id to mutableSetOf<Int>() }
69+
val supportedBy = this.bricks.associate { it.id to mutableSetOf<Int>() }
70+
71+
this.bricks.forEachIndexed { j, upperBrick ->
72+
this.bricks.subList(0, j).forEachIndexed { i, lowerBrick ->
73+
if (upperBrick.overlapsWith(lowerBrick) && upperBrick.a.z == lowerBrick.b.z + 1) {
74+
supports[i]!! += j
75+
supportedBy[j]!! += i
76+
}
12377
}
124-
if (print) println("Made $counter bricks fall")
12578
}
12679

127-
private fun tryMakeNextBrickFall(): Boolean {
128-
val brickFall = findNextBrickToFall() ?: return false
80+
var total = 0
12981

130-
remove(brickFall.oldBrick)
131-
add(brickFall.newBrick())
82+
this.bricks.forEachIndexed { i, brick ->
83+
val q: Deque<Int> = LinkedList(supports[brick.id]!!.filter { supportedBy[it]!!.size == 1 })
13284

133-
lastCheckedMinZ = brickFall.oldBrick.minZ
134-
return true
135-
}
85+
val falling = q.toMutableSet()
86+
falling.add(i)
13687

137-
private fun findNextBrickToFall(): BrickFall? = bricksByMinZ
138-
.tailMap(lastCheckedMinZ) // do not check below last checked minZ
139-
.values
140-
.asSequence()
141-
.flatten()
142-
.firstNotNullOfOrNull { brick -> findFallAmount(brick)?.let { fall -> BrickFall(brick, fall) } }
88+
while (q.isNotEmpty()) {
89+
val j = q.pop()
90+
for (k in (supports[j]!! - falling)) {
91+
if (supportedBy[k]!!.all { falling.contains(it) }) {
92+
q.add(k)
93+
falling.add(k)
94+
}
95+
}
96+
}
14397

144-
private fun findFallAmount(brick: Brick): Int? {
145-
val zAfterFall = brick.xyPoints.maxOf { xy -> minZAfterFallAt(brick, xy) }
146-
return (brick.minZ - zAfterFall).takeIf { it > 0 }
98+
total += falling.size - 1
14799
}
148100

149-
private fun minZAfterFallAt(brick: Brick, xy: Point2D): Int {
150-
val bricksBelow = bricksAt(xy).headSet(brick)
151-
if (bricksBelow.isEmpty()) {
152-
return 1
153-
}
154-
return bricksBelow.last().maxZ + 1
155-
}
101+
return total
156102
}
157103

158-
override fun part1(): Int {
159-
// https://www.youtube.com/watch?v=imz7uexX394
160-
161-
val removal = Snapshot(this.bricks)
162-
.makeBricksFall()
163-
.findBrickRemovalCounts()
164-
165-
println(removal)
104+
}
166105

167-
return removal
168-
.values
169-
.count { it == 0 }
170-
}
171106

172-
override fun part2(): Int {
173-
return Snapshot(this.bricks)
174-
.makeBricksFall()
175-
.findBrickRemovalCounts()
176-
.values
177-
.sum()
178-
}
179107

180-
}

src/test/kotlin/tr/emreone/adventofcode/days/Day22Test.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal class Day22Test {
1010
fun `execute_tests`() {
1111
solve<Day22>(false) {
1212
Resources.resourceAsList("day22_example.txt")
13-
.joinToString("\n") part1 5 // part2 7
13+
.joinToString("\n") part1 5 part2 7
1414
}
1515
}
1616

0 commit comments

Comments
 (0)