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 raw: Vec<_> = input.lines().map(str::as_bytes).collect();
33    let default = [U256::default(); HEIGHT];
34    let mut grid = default;
35
36    for (y, row) in raw.iter().enumerate() {
37        for (x, col) in row.iter().enumerate() {
38            if *col == b'#' {
39                grid[offset + y].set_bit(offset + x);
40            }
41        }
42    }
43
44    Input { grid, north: default, south: default, west: default, east: default }
45}
46
47pub fn part1(input: &Input) -> usize {
48    let mut input = *input;
49    let mut order = [North, South, West, East];
50
51    for _ in 0..10 {
52        step(&mut input, &mut order);
53    }
54
55    // Find the total number of elves and the bounding rectangle.
56    let grid = input.grid;
57    let elves = grid.iter().flat_map(U256::as_array).map(u8::count_ones).sum::<u32>() as usize;
58
59    // Vertical bounds.
60    let min_y = grid.iter().position(U256::non_zero).unwrap();
61    let max_y = grid.iter().rposition(U256::non_zero).unwrap();
62
63    // Horizontal bounds.
64    let array = grid.iter().fold(U256::default(), |acc, &n| acc.or(n)).as_array();
65    let left = array.iter().position(|&e| e != 0).unwrap();
66    let right = array.iter().rposition(|&e| e != 0).unwrap();
67
68    let min_x = 8 * left + array[left].leading_zeros() as usize;
69    let max_x = 8 * right + (7 - array[right].trailing_zeros()) as usize;
70
71    // Empty ground tiles.
72    (max_x - min_x + 1) * (max_y - min_y + 1) - elves
73}
74
75pub fn part2(input: &Input) -> u32 {
76    let mut input = *input;
77    let mut order = [North, South, West, East];
78    let mut count = 1;
79
80    while step(&mut input, &mut order) {
81        count += 1;
82    }
83
84    count
85}
86
87fn step(input: &mut Input, order: &mut [Direction]) -> bool {
88    let Input { grid, north, south, west, east } = input;
89    // Optimization to avoid processing empty rows.
90    let start = grid.iter().position(U256::non_zero).unwrap() - 1;
91    let end = grid.iter().rposition(U256::non_zero).unwrap() + 2;
92
93    let mut moved = false;
94
95    let mut prev;
96    // Find horizontal neighbors in each row. To make movement calculations easier
97    // we invert so that a 1 bit means movement is *possible*.
98    let mut cur = grid[0].shr().or(grid[0]).or(grid[0].shl()).not();
99    let mut next = grid[1].shr().or(grid[1]).or(grid[1].shl()).not();
100
101    for i in start..end {
102        // Calculating neighbors is relatively expensive so reuse results between rows.
103        prev = cur;
104        cur = next;
105        next = grid[i + 1].shr().or(grid[i + 1]).or(grid[i + 1].shl()).not();
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 = north[i];
150        let down = south[i];
151        let left = west[i];
152        let right = east[i];
153        north[i] = north[i].and(down.not());
154        south[i] = south[i].and(up.not());
155        west[i] = west[i].and(right.not());
156        east[i] = east[i].and(left.not());
157    }
158
159    for i in start..end {
160        // Stationary elves.
161        let same =
162            grid[i].and(north[i - 1].or(south[i + 1]).or(west[i].shr()).or(east[i].shl()).not());
163        // Moving elves.
164        let change = north[i].or(south[i]).or(west[i]).or(east[i]);
165        grid[i] = same.or(change);
166        moved |= change.non_zero();
167    }
168
169    // Rotate the order of movement proposals for the next turn.
170    order.rotate_left(1);
171    moved
172}
173
174#[cfg(not(feature = "simd"))]
175mod implementation {
176    /// Duct tape two `u128`s together.
177    #[derive(Clone, Copy, Default)]
178    pub(super) struct U256 {
179        left: u128,
180        right: u128,
181    }
182
183    impl U256 {
184        pub(super) fn set_bit(&mut self, offset: usize) {
185            if offset < 128 {
186                self.left |= 1 << (127 - offset);
187            } else {
188                self.right |= 1 << (255 - offset);
189            }
190        }
191
192        pub(super) fn as_array(&self) -> [u8; 32] {
193            let mut result = [0; 32];
194            result[..16].copy_from_slice(&self.left.to_be_bytes());
195            result[16..].copy_from_slice(&self.right.to_be_bytes());
196            result
197        }
198
199        pub(super) fn non_zero(&self) -> bool {
200            self.left != 0 || self.right != 0
201        }
202
203        pub(super) fn shl(self) -> U256 {
204            U256 { left: (self.left << 1) | (self.right >> 127), right: (self.right << 1) }
205        }
206
207        pub(super) fn shr(self) -> U256 {
208            U256 { left: (self.left >> 1), right: (self.left << 127) | (self.right >> 1) }
209        }
210
211        pub(super) fn and(self, rhs: U256) -> U256 {
212            U256 { left: self.left & rhs.left, right: self.right & rhs.right }
213        }
214
215        pub(super) fn or(self, rhs: U256) -> U256 {
216            U256 { left: self.left | rhs.left, right: self.right | rhs.right }
217        }
218
219        pub(super) fn not(self) -> U256 {
220            U256 { left: !self.left, right: !self.right }
221        }
222    }
223}
224
225#[cfg(feature = "simd")]
226mod implementation {
227    use std::simd::*;
228
229    #[derive(Clone, Copy, Default)]
230    pub(super) struct U256 {
231        v: Simd<u8, 32>,
232    }
233
234    impl U256 {
235        pub(super) fn set_bit(&mut self, offset: usize) {
236            self.v[offset / 8] |= 1 << (7 - offset % 8);
237        }
238
239        pub(super) fn as_array(&self) -> [u8; 32] {
240            self.v.to_array()
241        }
242
243        pub(super) fn non_zero(&self) -> bool {
244            self.v != Simd::splat(0)
245        }
246
247        pub(super) fn shl(self) -> U256 {
248            U256 { v: (self.v << 1) | (self.v.shift_elements_left::<1>(0) >> 7) }
249        }
250
251        pub(super) fn shr(self) -> U256 {
252            U256 { v: (self.v >> 1) | (self.v.shift_elements_right::<1>(0) << 7) }
253        }
254
255        pub(super) fn and(self, rhs: U256) -> U256 {
256            U256 { v: self.v & rhs.v }
257        }
258
259        pub(super) fn or(self, rhs: U256) -> U256 {
260            U256 { v: self.v | rhs.v }
261        }
262
263        pub(super) fn not(self) -> U256 {
264            U256 { v: !self.v }
265        }
266    }
267}