|
1 | 1 | import Solver from "../../../../../website/src/components/Solver.js"
|
| 2 | +import Literate from "../../../../../website/src/components/Literate.js" |
2 | 3 |
|
3 | 4 | # Day 22: Sand Slabs
|
4 | 5 |
|
@@ -60,7 +61,7 @@ Another vital operation involves determining whether two bricks collide with eac
|
60 | 61 | 1D line segments on X axis collide with each other when:
|
61 | 62 |
|
62 | 63 | ```scala 3
|
63 |
| -maxX >= otherMinX && otherMaxX >= minX |
| 64 | +maxX >= otherMinX && otherMaxX >= minX |
64 | 65 | ```
|
65 | 66 |
|
66 | 67 | We are not guaranteed the order of coordinates, so we have to determine min values and max values ourselves. Let's
|
@@ -101,92 +102,85 @@ def collidesWith(other: Brick): Boolean =
|
101 | 102 |
|
102 | 103 | ### Dropping groups of bricks
|
103 | 104 |
|
104 |
| -As the input is a snapshot of falling bricks, we must first drop them all to a stationary position. Additionally, since |
105 |
| -we will determine the collisions at this point, we'll create a `Map` that will associate each brick with the bricks |
106 |
| -supporting it. Let's define the `dropBricks` function. |
| 105 | +As the input is a snapshot of falling bricks, we must first drop them all to a stationary position. |
107 | 106 |
|
108 |
| -First, sort the bricks by the `z` axis to handle the falling order. This is necessary because the input doesn't |
109 |
| -guarantee the order of the bricks: |
| 107 | +First, define a way to check if a brick has collided with the ground, determined with a simple check on the `z` axis: |
110 | 108 |
|
111 | 109 | ```scala 3
|
112 |
| -val bricksByZAsc = bricks.sortBy(brick => (brick.start.z) min (brick.end.z)) |
| 110 | +def collidesWithGround(brick: Brick): Boolean = |
| 111 | + brick.start.z == 0 || brick.end.z == 0 |
113 | 112 | ```
|
114 | 113 |
|
115 |
| -Next, initialize the collection of bricks to drop and the `Map` of already dropped ones: |
| 114 | +Next, since we will determine the collisions at this point, we'll create a `Map` that will associate each brick with the bricks supporting it. |
| 115 | + |
| 116 | +Let's define the `dropBricks` function that can do this: |
| 117 | + |
| 118 | +<Literate> |
116 | 119 |
|
117 | 120 | ```scala 3
|
118 | 121 | import scala.collection.mutable
|
119 | 122 |
|
120 |
| -val remainingBricks = mutable.Stack.from(bricksByZAsc) |
121 |
| -val droppedBricks = mutable.Map[Brick, Set[Brick]]() |
| 123 | +def dropBricks(bricks: Seq[Brick]): Map[Brick, Set[Brick]] = { |
122 | 124 | ```
|
123 | 125 |
|
124 |
| -Then, loop over the remaining bricks with `while (remainingBricks.nonEmpty)`. On each iteration, simulate the fall of a |
125 |
| -single brick: |
| 126 | +First, sort the bricks by the `z` axis to handle the falling order. This is necessary because the input doesn't |
| 127 | +guarantee the order of the bricks: |
126 | 128 |
|
127 | 129 | ```scala 3
|
128 |
| -val brick = remainingBricks.pop() |
129 |
| -val brickMovedDown = brick.moveDown |
| 130 | + val bricksByZAsc = bricks.sortBy(brick => (brick.start.z) min (brick.end.z)) |
130 | 131 | ```
|
131 | 132 |
|
132 |
| -Now, determine whether the brick is stationary. "Stationary" means that it either collides with the ground or another |
133 |
| -brick. If it is stationary, put it into the `droppedBricks` along with the dropped bricks that collide with it. If not, |
134 |
| -put it back into the `remainingBricks` to move it further down in the next step: |
| 133 | +Next, initialize the `Stack` of bricks to drop and the `Map` of already dropped ones: |
135 | 134 |
|
136 | 135 | ```scala 3
|
137 |
| -if (collidesWithGround(brickMovedDown) || collidingBricks.nonEmpty) |
138 |
| - droppedBricks.put(brick, collidingBricks) |
139 |
| -else |
140 |
| - remainingBricks.push(brickMovedDown) |
| 136 | + val remainingBricks = mutable.Stack.from(bricksByZAsc) |
| 137 | + val droppedBricks = mutable.Map[Brick, Set[Brick]]() |
141 | 138 | ```
|
142 | 139 |
|
143 |
| -The collision with the ground is determined with a simple check on the `z` axis: |
| 140 | +Then, loop over the remaining bricks with `while (remainingBricks.nonEmpty)`. |
144 | 141 |
|
145 | 142 | ```scala 3
|
146 |
| -def collidesWithGround(brick: Brick): Boolean = |
147 |
| - brick.start.z == 0 || brick.end.z == 0 |
| 143 | + while (remainingBricks.nonEmpty) { |
148 | 144 | ```
|
149 | 145 |
|
150 |
| -The `Set` of colliding dropped bricks is determined using the previously defined `Brick#collidesWith` method: |
| 146 | +On each iteration, simulate the fall of a single brick: |
151 | 147 |
|
152 |
| -```scala 3 |
153 |
| -val collidingBricks = droppedBricks.collect { |
154 |
| - case (droppedBrick, _) if brickMovedDown.collidesWith(droppedBrick) => |
155 |
| - droppedBrick |
156 |
| -}.toSet |
| 148 | +```scala |
| 149 | + val brick = remainingBricks.pop() |
| 150 | + val brickMovedDown = brick.moveDown |
157 | 151 | ```
|
158 | 152 |
|
159 |
| -After all the bricks finish falling, return the `droppedBricks` as an immutable `Map`. The full function looks like |
160 |
| -this: |
| 153 | +First, determine if there are any colliding bricks from the currently known `droppedBricks`. We can produce this as a `Set`, with contents determined using the previously defined `Brick#collidesWith` method: |
161 | 154 |
|
162 | 155 | ```scala 3
|
163 |
| -import scala.collection.mutable |
164 |
| - |
165 |
| -def dropBricks(bricks: Seq[Brick]): Map[Brick, Set[Brick]] = { |
166 |
| - val bricksByZAsc = bricks.sortBy(brick => (brick.start.z) min (brick.end.z)) |
167 |
| - val remainingBricks = mutable.Stack.from(bricksByZAsc) |
168 |
| - val droppedBricks = mutable.Map[Brick, Set[Brick]]() |
169 |
| - |
170 |
| - while (remainingBricks.nonEmpty) { |
171 |
| - val brick = remainingBricks.pop() |
172 |
| - val brickMovedDown = brick.moveDown |
173 | 156 | val collidingBricks = droppedBricks.collect {
|
174 | 157 | case (droppedBrick, _) if brickMovedDown.collidesWith(droppedBrick) =>
|
175 | 158 | droppedBrick
|
176 | 159 | }.toSet
|
| 160 | +``` |
| 161 | + |
| 162 | +Now, determine whether the brick is stationary. "Stationary" means that it either collides with the ground or another |
| 163 | +brick. If it is stationary, put it into the `droppedBricks` along with the dropped bricks that collide with it. If not, |
| 164 | +put it back into the `remainingBricks` to move it further down in the next step: |
| 165 | + |
| 166 | +```scala 3 |
177 | 167 | if (collidesWithGround(brickMovedDown) || collidingBricks.nonEmpty)
|
178 | 168 | droppedBricks.put(brick, collidingBricks)
|
179 | 169 | else
|
180 | 170 | remainingBricks.push(brickMovedDown)
|
181 | 171 | }
|
| 172 | +``` |
| 173 | + |
| 174 | +After all the bricks finish falling, return the `droppedBricks` by converting to an immutable `Map`. |
182 | 175 |
|
| 176 | +```scala 3 |
183 | 177 | droppedBricks.toMap
|
184 | 178 | }
|
185 |
| - |
186 |
| -def collidesWithGround(brick: Brick): Boolean = |
187 |
| - brick.start.z == 0 || brick.end.z == 0 |
188 | 179 | ```
|
189 | 180 |
|
| 181 | +</Literate> |
| 182 | + |
| 183 | + |
190 | 184 | ### Determining the disintegrable bricks
|
191 | 185 |
|
192 | 186 | Now, let's get back to the core challenge. We want to figure out how many bricks we can safely disintegrate. A brick is
|
|
0 commit comments