aoc/year2022/
day23.rs

1//! # Unstable Diffusion
2//!
3//! We represent elves as bits in an integer then use bitwise operations to efficiently figure
4//! out the movement for multiple elves at once.
5use Direction::*;
6
7#[cfg(not(feature = "simd"))]
8use scalar::U256;
9#[cfg(feature = "simd")]
10use simd::U256;
11
12/// The initial grid is 70 x 70. Elves stop moving when no other elf is adjacent so the grid
13/// will expand at most 70 in any direction, giving 70 + 70 + 70 = 210 total.
14const HEIGHT: usize = 210;
15
16enum Direction {
17    North,
18    South,
19    West,
20    East,
21}
22
23#[derive(Clone, Copy)]
24pub struct Input {
25    grid: [U256; HEIGHT],
26    north: [U256; HEIGHT],
27    south: [U256; HEIGHT],
28    west: [U256; HEIGHT],
29    east: [U256; HEIGHT],
30}
31
32/// Converts the ASCII grid into a bit per elf.
33pub fn parse(input: &str) -> Input {
34    // Enough buffer so that elves won't overflow the edges of the grid.
35    let offset = 70;
36    let raw: Vec<_> = input.lines().map(str::as_bytes).collect();
37    let default = [U256::default(); HEIGHT];
38    let mut grid = default;
39
40    for (y, row) in raw.iter().enumerate() {
41        for (x, col) in row.iter().enumerate() {
42            if *col == b'#' {
43                grid[offset + y].set_bit(offset + x);
44            }
45        }
46    }
47
48    Input { grid, north: default, south: default, west: default, east: default }
49}
50
51pub fn part1(input: &Input) -> usize {
52    let mut input = *input;
53    let mut order = [North, South, West, East];
54
55    for _ in 0..10 {
56        step(&mut input, &mut order);
57    }
58
59    // Find the total number of elves and the bounding rectangle.
60    let grid = input.grid;
61    let elves = grid.iter().flat_map(U256::as_array).map(u8::count_ones).sum::<u32>() as usize;
62
63    // Vertical bounds.
64    let min_y = grid.iter().position(U256::non_zero).unwrap();
65    let max_y = grid.iter().rposition(U256::non_zero).unwrap();
66
67    // Horizontal bounds.
68    let array = grid.iter().fold(U256::default(), |acc, &n| acc.or(n)).as_array();
69    let left = array.iter().position(|&e| e != 0).unwrap();
70    let right = array.iter().rposition(|&e| e != 0).unwrap();
71
72    let min_x = 8 * left + array[left].leading_zeros() as usize;
73    let max_x = 8 * right + (7 - array[right].trailing_zeros()) as usize;
74
75    // Empty ground tiles.
76    (max_x - min_x + 1) * (max_y - min_y + 1) - elves
77}
78
79pub fn part2(input: &Input) -> u32 {
80    let mut input = *input;
81    let mut order = [North, South, West, East];
82    let mut moved = true;
83    let mut count = 0;
84
85    while moved {
86        moved = step(&mut input, &mut order);
87        count += 1;
88    }
89
90    count
91}
92
93fn step(input: &mut Input, order: &mut [Direction]) -> bool {
94    let Input { grid, north, south, west, east } = input;
95    // Optimization to avoid processing empty rows.
96    let start = grid.iter().position(U256::non_zero).unwrap() - 1;
97    let end = grid.iter().rposition(U256::non_zero).unwrap() + 2;
98
99    let mut moved = false;
100
101    let mut prev;
102    // Find horizontal neighbors in each row. To make movement calculations easier
103    // we invert so that a bit is 1 is movement is *possible*.
104    let mut cur = grid[0].shr().or(grid[0]).or(grid[0].shl()).not();
105    let mut next = grid[1].shr().or(grid[1]).or(grid[1].shl()).not();
106
107    for i in start..end {
108        // Calculating neighbors is relatively expensive so re-use results between rows.
109        prev = cur;
110        cur = next;
111        next = grid[i + 1].shr().or(grid[i + 1]).or(grid[i + 1].shl()).not();
112
113        let mut up = prev;
114        let mut down = next;
115        // Find neighbors in vertical columns.
116        let vertical = grid[i - 1].or(grid[i]).or(grid[i + 1]).not();
117        let mut left = vertical.shr();
118        let mut right = vertical.shl();
119        // Elves need at least 1 neighbor to propose moving.
120        let mut remaining = grid[i].and(up.and(down).and(left).and(right).not());
121
122        // Consider each direction one at a time, removing any elves who propose it.
123        for direction in &*order {
124            match direction {
125                North => {
126                    up = up.and(remaining);
127                    remaining = remaining.and(up.not());
128                }
129                South => {
130                    down = down.and(remaining);
131                    remaining = remaining.and(down.not());
132                }
133                West => {
134                    left = left.and(remaining);
135                    remaining = remaining.and(left.not());
136                }
137                East => {
138                    right = right.and(remaining);
139                    remaining = remaining.and(right.not());
140                }
141            }
142        }
143
144        // Copy final proposals to an array for each direction.
145        north[i - 1] = up;
146        south[i + 1] = down;
147        west[i] = left.shl();
148        east[i] = right.shr();
149    }
150
151    // Elves that propose moving to the same spot cancel each other out and no-one moves.
152    // Due to the movement rules we only need to check horizontal and vertical movement into
153    // the same spot (horizontal and vertical movement can never collide with each other).
154    for i in start..end {
155        let up = north[i];
156        let down = south[i];
157        let left = west[i];
158        let right = east[i];
159        north[i] = north[i].and(down.not());
160        south[i] = south[i].and(up.not());
161        west[i] = west[i].and(right.not());
162        east[i] = east[i].and(left.not());
163    }
164
165    for i in start..end {
166        // Stationary elves.
167        let same =
168            grid[i].and(north[i - 1].or(south[i + 1]).or(west[i].shr()).or(east[i].shl()).not());
169        // Moving elves.
170        let change = north[i].or(south[i]).or(west[i]).or(east[i]);
171        grid[i] = same.or(change);
172        moved |= change.non_zero();
173    }
174
175    // Rotate the order of movement proposals for the next turn.
176    order.rotate_left(1);
177    moved
178}
179
180#[cfg(not(feature = "simd"))]
181mod scalar {
182    /// Duct tape two `u128`s together.
183    #[derive(Clone, Copy, Default)]
184    pub(super) struct U256 {
185        left: u128,
186        right: u128,
187    }
188
189    impl U256 {
190        pub(super) fn set_bit(&mut self, offset: usize) {
191            if offset < 128 {
192                self.left |= 1 << (127 - offset);
193            } else {
194                self.right |= 1 << (255 - offset);
195            }
196        }
197
198        pub(super) fn as_array(&self) -> [u8; 32] {
199            [self.left.to_be_bytes(), self.right.to_be_bytes()].concat().try_into().unwrap()
200        }
201
202        pub(super) fn non_zero(&self) -> bool {
203            self.left != 0 || self.right != 0
204        }
205
206        pub(super) fn shl(self) -> U256 {
207            U256 { left: (self.left << 1) | (self.right >> 127), right: (self.right << 1) }
208        }
209
210        pub(super) fn shr(self) -> U256 {
211            U256 { left: (self.left >> 1), right: (self.left << 127) | (self.right >> 1) }
212        }
213
214        pub(super) fn and(self, rhs: U256) -> U256 {
215            U256 { left: self.left & rhs.left, right: self.right & rhs.right }
216        }
217
218        pub(super) fn or(self, rhs: U256) -> U256 {
219            U256 { left: self.left | rhs.left, right: self.right | rhs.right }
220        }
221
222        pub(super) fn not(self) -> U256 {
223            U256 { left: !self.left, right: !self.right }
224        }
225    }
226}
227
228#[cfg(feature = "simd")]
229mod simd {
230    use std::simd::*;
231
232    #[derive(Clone, Copy, Default)]
233    pub(super) struct U256 {
234        v: Simd<u8, 32>,
235    }
236
237    impl U256 {
238        pub(super) fn set_bit(&mut self, offset: usize) {
239            self.v[offset / 8] |= 1 << (7 - offset % 8);
240        }
241
242        pub(super) fn as_array(&self) -> [u8; 32] {
243            self.v.to_array()
244        }
245
246        pub(super) fn non_zero(&self) -> bool {
247            self.v != Simd::splat(0)
248        }
249
250        pub(super) fn shl(self) -> U256 {
251            U256 { v: (self.v << 1) | (self.v.shift_elements_left::<1>(0) >> 7) }
252        }
253
254        pub(super) fn shr(self) -> U256 {
255            U256 { v: (self.v >> 1) | (self.v.shift_elements_right::<1>(0) << 7) }
256        }
257
258        pub(super) fn and(self, rhs: U256) -> U256 {
259            U256 { v: self.v & rhs.v }
260        }
261
262        pub(super) fn or(self, rhs: U256) -> U256 {
263            U256 { v: self.v | rhs.v }
264        }
265
266        pub(super) fn not(self) -> U256 {
267            U256 { v: !self.v }
268        }
269    }
270}