Skip to content

Commit 4f164fa

Browse files
committed
Document Year 2022 Day 14
1 parent b4d7927 commit 4f164fa

File tree

1 file changed

+58
-13
lines changed

1 file changed

+58
-13
lines changed

src/year2022/day14.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
1+
//! # Regolith Reservoir
2+
//!
3+
//! We could simulate each grain of sand falling one at a time, for example:
4+
//!
5+
//! ```none
6+
//! #o# # # # # # #
7+
//! # # #o# # # # #
8+
//! # # => # # => #o# => # #
9+
//! # # # # # # #o#
10+
//! ### ### ### ###
11+
//! ```
12+
//!
13+
//! In this example it would take 4 steps to simulate the first grain, 3 steps for the second
14+
//! and so on. More generally it would take `∑n` = `n * (n + 1) / 2` or `O(n²)` complexity for
15+
//! the whole pile.
16+
//!
17+
//! We instead simulate in `O(n)` complexity by recursively check each grain's underneath
18+
//! neighbors until we have a conclusive result then propagating that back up the call stack,
19+
//! for example:
20+
//!
21+
//! ```none
22+
//! #?# #?# #?# #?# #?# #?# #?# #o#
23+
//! # # #?# #?# #?# #?# #?# #o# #o#
24+
//! # # => # # => #?# => #?# => #?# => #o# => #o# => #o#
25+
//! # # # # # # #?# #o# #o# #o# #o#
26+
//! ### ### ### ### ### ### ### ###
27+
//! ```
28+
//!
29+
//! We model the cave as a grid in the possible states:
30+
//! * `Air` Empty blocks, treated as unknown status when checking underneath neighbors.
31+
//! * `Falling` Grains of sand that will continue to fall continously forever.
32+
//! * `Stopped` Both original rock walls and any grains of sand that have come to rest.
133
use crate::util::parse::*;
2-
use Kind::*;
334

435
#[derive(Clone, Copy, PartialEq, Eq)]
536
enum Kind {
@@ -20,67 +51,81 @@ pub struct Cave {
2051

2152
impl Cave {
2253
fn fall(&mut self, index: usize) -> Kind {
54+
// Check in order: center, left then right
2355
let result = self.check(index + self.width)
2456
&& self.check(index + self.width - 1)
2557
&& self.check(index + self.width + 1);
2658

59+
// If all 3 bottom neighbors are stopped then so are we.
60+
// Cache the result into the grid then propagate result back up the call stack.
2761
if result {
2862
self.count += 1;
29-
self.kind[index] = Stopped;
30-
Stopped
63+
self.kind[index] = Kind::Stopped;
64+
Kind::Stopped
3165
} else {
32-
self.kind[index] = Falling;
33-
Falling
66+
self.kind[index] = Kind::Falling;
67+
Kind::Falling
3468
}
3569
}
3670

71+
// Returns `true` if cell is stopped.
3772
fn check(&mut self, index: usize) -> bool {
3873
let kind = if index >= self.size {
74+
// If we've reached the "floor" then return that.
3975
self.floor
40-
} else if self.kind[index] == Air {
76+
} else if self.kind[index] == Kind::Air {
77+
// If we're unknown then recursively check our own underneath neighbors
4178
self.fall(index)
4279
} else {
80+
// Otherwise use the cached value.
4381
self.kind[index]
4482
};
45-
kind == Stopped
83+
kind == Kind::Stopped
4684
}
4785
}
4886

87+
/// Creates a 2D grid cave exactly the maximum possible size.
4988
pub fn parse(input: &str) -> Cave {
5089
let unsigned = |line: &str| line.iter_unsigned().collect();
5190
let points: Vec<Vec<usize>> = input.lines().map(unsigned).collect();
5291
let max_y = points.iter().flat_map(|row| row.iter().skip(1).step_by(2)).max().unwrap();
53-
let width = 2 * max_y + 5;
92+
// Floor is 2 below the bottommost wall.
5493
let height = max_y + 2;
94+
// Allow enough horizontal room to spread out.
95+
let width = 2 * height + 1;
5596
let size = width * height;
56-
let mut kind = vec![Air; size];
97+
let mut kind = vec![Kind::Air; size];
5798

99+
// Draw each of the walls.
58100
for row in points {
59101
for window in row.windows(4).step_by(2) {
60102
if let &[x1, y1, x2, y2] = window {
61103
for x in x1.min(x2)..=x1.max(x2) {
62104
for y in y1.min(y2)..=y1.max(y2) {
63-
kind[(width * y) + (x + height - 500)] = Stopped;
105+
kind[(width * y) + (x + height - 500)] = Kind::Stopped;
64106
}
65107
}
66108
}
67109
}
68110
}
69111

70-
Cave { width, height, size, kind, floor: Air, count: 0 }
112+
Cave { width, height, size, kind, floor: Kind::Air, count: 0 }
71113
}
72114

115+
/// If a grain of sand reaches the floor it will fall forever.
73116
pub fn part1(input: &Cave) -> u32 {
74-
simulate(input, Falling)
117+
simulate(input, Kind::Falling)
75118
}
76119

120+
/// The floor is solid rock.
77121
pub fn part2(input: &Cave) -> u32 {
78-
simulate(input, Stopped)
122+
simulate(input, Kind::Stopped)
79123
}
80124

81125
fn simulate(input: &Cave, floor: Kind) -> u32 {
82126
let mut cave = input.clone();
83127
cave.floor = floor;
128+
// Height is also the x coordinate of the central starting location for grains.
84129
cave.fall(cave.height);
85130
cave.count
86131
}

0 commit comments

Comments
 (0)