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