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 Direction::*;
6use 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    let mut prev;
95    // Find horizontal neighbors in each row. To make movement calculations easier
96    // we invert so that a 1 bit means movement is *possible*.
97    let mut cur = grid[0].shr().or(grid[0]).or(grid[0].shl()).not();
98    let mut next = grid[1].shr().or(grid[1]).or(grid[1].shl()).not();
99
100    for i in start..end {
101        // Calculating neighbors is relatively expensive so reuse results between rows.
102        prev = cur;
103        cur = next;
104        next = grid[i + 1].shr().or(grid[i + 1]).or(grid[i + 1].shl()).not();
105
106        let mut up = prev;
107        let mut down = next;
108        // Find neighbors in vertical columns.
109        let vertical = grid[i - 1].or(grid[i]).or(grid[i + 1]).not();
110        let mut left = vertical.shr();
111        let mut right = vertical.shl();
112        // Elves need at least 1 neighbor to propose moving.
113        let mut remaining = grid[i].and(up.and(down).and(left).and(right).not());
114
115        // Consider each direction one at a time, removing any elves who propose it.
116        for direction in &*order {
117            match direction {
118                North => {
119                    up = up.and(remaining);
120                    remaining = remaining.and(up.not());
121                }
122                South => {
123                    down = down.and(remaining);
124                    remaining = remaining.and(down.not());
125                }
126                West => {
127                    left = left.and(remaining);
128                    remaining = remaining.and(left.not());
129                }
130                East => {
131                    right = right.and(remaining);
132                    remaining = remaining.and(right.not());
133                }
134            }
135        }
136
137        // Copy final proposals to an array for each direction.
138        north[i - 1] = up;
139        south[i + 1] = down;
140        west[i] = left.shl();
141        east[i] = right.shr();
142    }
143
144    // Elves that propose moving to the same spot cancel each other out and no-one moves.
145    // Due to the movement rules we only need to check horizontal and vertical movement into
146    // the same spot (horizontal and vertical movement can never collide with each other).
147    for i in start..end {
148        let up = north[i];
149        let down = south[i];
150        let left = west[i];
151        let right = east[i];
152        north[i] = north[i].and(down.not());
153        south[i] = south[i].and(up.not());
154        west[i] = west[i].and(right.not());
155        east[i] = east[i].and(left.not());
156    }
157
158    for i in start..end {
159        // Stationary elves.
160        let same =
161            grid[i].and(north[i - 1].or(south[i + 1]).or(west[i].shr()).or(east[i].shl()).not());
162        // Moving elves.
163        let change = north[i].or(south[i]).or(west[i]).or(east[i]);
164        grid[i] = same.or(change);
165        moved |= change.non_zero();
166    }
167
168    // Rotate the order of movement proposals for the next turn.
169    order.rotate_left(1);
170    moved
171}
172
173#[cfg(not(feature = "simd"))]
174mod implementation {
175    /// Duct tape two `u128`s together.
176    #[derive(Clone, Copy, Default)]
177    pub(super) struct U256 {
178        left: u128,
179        right: u128,
180    }
181
182    impl U256 {
183        pub(super) fn set_bit(&mut self, offset: usize) {
184            if offset < 128 {
185                self.left |= 1 << (127 - offset);
186            } else {
187                self.right |= 1 << (255 - offset);
188            }
189        }
190
191        pub(super) fn as_array(&self) -> [u8; 32] {
192            let mut result = [0; 32];
193            result[..16].copy_from_slice(&self.left.to_be_bytes());
194            result[16..].copy_from_slice(&self.right.to_be_bytes());
195            result
196        }
197
198        pub(super) fn non_zero(&self) -> bool {
199            self.left != 0 || self.right != 0
200        }
201
202        pub(super) fn shl(self) -> U256 {
203            U256 { left: (self.left << 1) | (self.right >> 127), right: (self.right << 1) }
204        }
205
206        pub(super) fn shr(self) -> U256 {
207            U256 { left: (self.left >> 1), right: (self.left << 127) | (self.right >> 1) }
208        }
209
210        pub(super) fn and(self, rhs: U256) -> U256 {
211            U256 { left: self.left & rhs.left, right: self.right & rhs.right }
212        }
213
214        pub(super) fn or(self, rhs: U256) -> U256 {
215            U256 { left: self.left | rhs.left, right: self.right | rhs.right }
216        }
217
218        pub(super) fn not(self) -> U256 {
219            U256 { left: !self.left, right: !self.right }
220        }
221    }
222}
223
224#[cfg(feature = "simd")]
225mod implementation {
226    use std::simd::*;
227
228    #[derive(Clone, Copy, Default)]
229    pub(super) struct U256 {
230        v: Simd<u8, 32>,
231    }
232
233    impl U256 {
234        pub(super) fn set_bit(&mut self, offset: usize) {
235            self.v[offset / 8] |= 1 << (7 - offset % 8);
236        }
237
238        pub(super) fn as_array(&self) -> [u8; 32] {
239            self.v.to_array()
240        }
241
242        pub(super) fn non_zero(&self) -> bool {
243            self.v != Simd::splat(0)
244        }
245
246        pub(super) fn shl(self) -> Self {
247            Self { v: (self.v << 1) | (self.v.shift_elements_left::<1>(0) >> 7) }
248        }
249
250        pub(super) fn shr(self) -> Self {
251            Self { v: (self.v >> 1) | (self.v.shift_elements_right::<1>(0) << 7) }
252        }
253
254        pub(super) fn and(self, rhs: Self) -> Self {
255            Self { v: self.v & rhs.v }
256        }
257
258        pub(super) fn or(self, rhs: Self) -> Self {
259            Self { v: self.v | rhs.v }
260        }
261
262        pub(super) fn not(self) -> Self {
263            Self { v: !self.v }
264        }
265    }
266}