Skip to main content

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