11package tr.emreone.adventofcode.days
22
33import tr.emreone.kotlin_utils.automation.Day
4- import tr.emreone.kotlin_utils.math.Point2D
54import tr.emreone.kotlin_utils.math.Point3D
65import java.util.*
6+ import kotlin.collections.ArrayDeque
77import kotlin.math.max
88import kotlin.math.min
99
1010class 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- }
0 commit comments