Skip to content

Commit 31f88d7

Browse files
committed
Document Year 2022 Day 23
1 parent 26e1fcc commit 31f88d7

File tree

1 file changed

+30
-3
lines changed

1 file changed

+30
-3
lines changed

src/year2022/day23.rs

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
//! # Unstable Diffusion
2+
//!
3+
//! We represent elves as bits in a integer then use bitwise operations to efficiently figure
4+
//! out the movement for multiple elves at once.
15
use self::Direction::*;
26
use std::ops::{BitAnd, BitAndAssign, BitOr, Not};
37

8+
/// The initial grid is 70 x 70. Elves stop moving when no other elf is adjacent so the grid
9+
/// will expand at most 70 in any direction, giving 70 + 70 + 70 = 210 total.
410
const HEIGHT: usize = 210;
511

12+
/// Duct tape two `u128`s together.
613
#[derive(Clone, Copy, Default)]
714
pub struct U256 {
815
left: u128,
@@ -26,6 +33,7 @@ impl U256 {
2633
self.left != 0 || self.right != 0
2734
}
2835

36+
/// Used to find the bounding rectangle for part one.
2937
fn min_set(&self) -> Option<u32> {
3038
if self.left != 0 {
3139
Some(self.left.leading_zeros())
@@ -36,6 +44,7 @@ impl U256 {
3644
}
3745
}
3846

47+
/// Used to find the bounding rectangle for part one.
3948
fn max_set(&self) -> Option<u32> {
4049
if self.right != 0 {
4150
Some(255 - self.right.trailing_zeros())
@@ -55,6 +64,7 @@ impl U256 {
5564
}
5665
}
5766

67+
/// Syntactic sugar to provide the regular `&`, `|` and `!` bitwise operator notation.
5868
impl BitAnd for U256 {
5969
type Output = U256;
6070

@@ -102,7 +112,9 @@ pub struct Input {
102112
east: [U256; HEIGHT],
103113
}
104114

115+
/// Converts the ASCII grid into a bit per elf.
105116
pub fn parse(input: &str) -> Input {
117+
// Enough buffer so that elves won't overflow the edges of the grid.
106118
let offset = 70;
107119
let raw: Vec<_> = input.lines().map(str::as_bytes).collect();
108120
let default = [U256::default(); HEIGHT];
@@ -127,6 +139,7 @@ pub fn part1(input: &Input) -> u32 {
127139
step(&mut input, &mut order);
128140
}
129141

142+
// Find the bounding rectangle.
130143
let grid = input.grid;
131144
let elves: u32 = grid.iter().map(U256::count_ones).sum();
132145
let min_x = grid.iter().filter_map(U256::min_set).min().unwrap();
@@ -153,27 +166,34 @@ pub fn part2(input: &Input) -> u32 {
153166

154167
fn step(input: &mut Input, order: &mut [Direction]) -> bool {
155168
let Input { grid, north, south, west, east } = input;
169+
// Optimization to avoid processing empty rows.
156170
let start = grid.iter().position(U256::non_zero).unwrap() - 1;
157171
let end = grid.iter().rposition(U256::non_zero).unwrap() + 2;
158172

159173
let mut moved = false;
160174

161175
let mut prev;
176+
// Find horizontal neighbors in each row. To make movement calculations easier
177+
// we invert so that a bit is 1 is movement is *possible*.
162178
let mut cur = !(grid[0].right_shift() | grid[0] | grid[0].left_shift());
163179
let mut next = !(grid[1].right_shift() | grid[1] | grid[1].left_shift());
164180

165181
for i in start..end {
182+
// Calculating neighbors is relatively expensive so re-use results between rows.
166183
prev = cur;
167184
cur = next;
168185
next = !(grid[i + 1].right_shift() | grid[i + 1] | grid[i + 1].left_shift());
169186

170187
let mut up = prev;
171188
let mut down = next;
172-
let horizontal = !(grid[i - 1] | grid[i] | grid[i + 1]);
173-
let mut left = horizontal.right_shift();
174-
let mut right = horizontal.left_shift();
189+
// Find neighours in vertical columns.
190+
let vertical = !(grid[i - 1] | grid[i] | grid[i + 1]);
191+
let mut left = vertical.right_shift();
192+
let mut right = vertical.left_shift();
193+
// Elves need at least 1 neighbor to propose moving.
175194
let mut remaining = grid[i] & !(up & down & left & right);
176195

196+
// Consider each direction one at a time, removing any elves who propose it.
177197
for direction in &*order {
178198
match direction {
179199
North => {
@@ -195,12 +215,16 @@ fn step(input: &mut Input, order: &mut [Direction]) -> bool {
195215
}
196216
}
197217

218+
// Copy final proposals to an array for each direction.
198219
north[i - 1] = up;
199220
south[i + 1] = down;
200221
west[i] = left.left_shift();
201222
east[i] = right.right_shift();
202223
}
203224

225+
// Elves that propose moving to the same spot cancel each other out and no-one moves.
226+
// Due to the movement rules we only need to check horizontal and vertical movement into
227+
// the same spot (horizontal and vertical movement can never collide with each other).
204228
for i in start..end {
205229
let up = north[i];
206230
let down = south[i];
@@ -213,13 +237,16 @@ fn step(input: &mut Input, order: &mut [Direction]) -> bool {
213237
}
214238

215239
for i in start..end {
240+
// Stationary elves.
216241
let same =
217242
grid[i] & !(north[i - 1] | south[i + 1] | west[i].right_shift() | east[i].left_shift());
243+
// Moving elves.
218244
let change = north[i] | south[i] | west[i] | east[i];
219245
grid[i] = same | change;
220246
moved |= change.non_zero();
221247
}
222248

249+
// Rotate the order of movement proposals for the next turn.
223250
order.rotate_left(1);
224251
moved
225252
}

0 commit comments

Comments
 (0)