1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
//! # Regolith Reservoir
//!
//! We could simulate each grain of sand falling one at a time, for example:
//!
//! ```none
//! #o# # # # # # #
//! # # #o# # # # #
//! # # => # # => #o# => # #
//! # # # # # # #o#
//! ### ### ### ###
//! ```
//!
//! In this example it would take 4 steps to simulate the first grain, 3 steps for the second
//! and so on. More generally it would take `∑n` = `n * (n + 1) / 2` or `O(n²)` complexity for
//! the whole pile.
//!
//! We instead simulate in `O(n)` complexity by recursively check each grain's underneath
//! neighbors until we have a conclusive result then propagating that back up the call stack,
//! for example:
//!
//! ```none
//! #?# #?# #?# #?# #?# #?# #?# #o#
//! # # #?# #?# #?# #?# #?# #o# #o#
//! # # => # # => #?# => #?# => #?# => #o# => #o# => #o#
//! # # # # # # #?# #o# #o# #o# #o#
//! ### ### ### ### ### ### ### ###
//! ```
//!
//! We model the cave as a grid in the possible states:
//! * `Air` Empty blocks, treated as unknown status when checking underneath neighbors.
//! * `Falling` Grains of sand that will continue to fall continously forever.
//! * `Stopped` Both original rock walls and any grains of sand that have come to rest.
use crate::util::parse::*;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Air,
Falling,
Stopped,
}
#[derive(Clone)]
pub struct Cave {
width: usize,
height: usize,
size: usize,
kind: Vec<Kind>,
floor: Kind,
count: u32,
}
impl Cave {
fn fall(&mut self, index: usize) -> Kind {
// Check in order: center, left then right
let result = self.check(index + self.width)
&& self.check(index + self.width - 1)
&& self.check(index + self.width + 1);
// If all 3 bottom neighbors are stopped then so are we.
// Cache the result into the grid then propagate result back up the call stack.
if result {
self.count += 1;
self.kind[index] = Kind::Stopped;
Kind::Stopped
} else {
self.kind[index] = Kind::Falling;
Kind::Falling
}
}
// Returns `true` if cell is stopped.
fn check(&mut self, index: usize) -> bool {
let kind = if index >= self.size {
// If we've reached the "floor" then return that.
self.floor
} else if self.kind[index] == Kind::Air {
// If we're unknown then recursively check our own underneath neighbors
self.fall(index)
} else {
// Otherwise use the cached value.
self.kind[index]
};
kind == Kind::Stopped
}
}
/// Creates a 2D grid cave exactly the maximum possible size.
pub fn parse(input: &str) -> Cave {
let unsigned = |line: &str| line.iter_unsigned().collect();
let points: Vec<Vec<usize>> = input.lines().map(unsigned).collect();
let max_y = points.iter().flat_map(|row| row.iter().skip(1).step_by(2)).max().unwrap();
// Floor is 2 below the bottommost wall.
let height = max_y + 2;
// Allow enough horizontal room to spread out.
let width = 2 * height + 1;
let size = width * height;
let mut kind = vec![Kind::Air; size];
// Draw each of the walls.
for row in points {
for window in row.windows(4).step_by(2) {
if let &[x1, y1, x2, y2] = window {
for x in x1.min(x2)..=x1.max(x2) {
for y in y1.min(y2)..=y1.max(y2) {
kind[(width * y) + (x + height - 500)] = Kind::Stopped;
}
}
}
}
}
Cave { width, height, size, kind, floor: Kind::Air, count: 0 }
}
/// If a grain of sand reaches the floor it will fall forever.
pub fn part1(input: &Cave) -> u32 {
simulate(input, Kind::Falling)
}
/// The floor is solid rock.
pub fn part2(input: &Cave) -> u32 {
simulate(input, Kind::Stopped)
}
fn simulate(input: &Cave, floor: Kind) -> u32 {
let mut cave = input.clone();
cave.floor = floor;
// Height is also the x coordinate of the central starting location for grains.
cave.fall(cave.height);
cave.count
}