Skip to main content

year2019/
day24.rs

1use utils::bit::{bitwise_count4, bitwise_count8};
2use utils::prelude::*;
3
4/// Simulating a cellular automaton on a recursive grid.
5#[derive(Clone, Debug)]
6pub struct Day24 {
7    initial: u32,
8    recursive_minutes: usize,
9}
10
11const SIDE: usize = 5;
12const TILE_COUNT: usize = SIDE * SIDE;
13const MIDDLE: usize = TILE_COUNT / 2;
14const GRID_MASK: u32 = (1 << TILE_COUNT) - 1;
15const EXAMPLE_MINUTES: usize = 10;
16const REAL_MINUTES: usize = 200;
17const LEVELS: usize = 2 * REAL_MINUTES + 3;
18const ROW_MASK: u32 = 0b1_1111;
19const COLUMN_MASK: u32 = 0b00001_00001_00001_00001_00001;
20
21impl Day24 {
22    pub fn new(input: &str, input_type: InputType) -> Result<Self, InputError> {
23        let mut initial = 0;
24        let mut lines = input.lines();
25
26        for row in 0..SIDE {
27            let Some(line) = lines.next() else {
28                return Err(InputError::new(input, 0, "expected exactly 5 rows"));
29            };
30            if line.len() != SIDE {
31                return Err(InputError::new(input, line, "expected exactly 5 columns"));
32            }
33
34            for (column, &byte) in line.as_bytes().iter().enumerate() {
35                match byte {
36                    b'.' => {}
37                    b'#' => initial |= 1 << (row * SIDE + column),
38                    _ => {
39                        return Err(InputError::new(
40                            input,
41                            &line.as_bytes()[column..column + 1],
42                            "expected '.' or '#'",
43                        ));
44                    }
45                }
46            }
47
48            if row == SIDE / 2 && Self::bug_at(initial, MIDDLE) != 0 {
49                return Err(InputError::new(
50                    input,
51                    &line.as_bytes()[SIDE / 2..],
52                    "expected '.' in the middle",
53                ));
54            }
55        }
56        if let Some(line) = lines.next() {
57            return Err(InputError::new(input, line, "expected exactly 5 rows"));
58        }
59
60        Ok(Self {
61            initial,
62            recursive_minutes: match input_type {
63                InputType::Example => EXAMPLE_MINUTES,
64                InputType::Real => REAL_MINUTES,
65            },
66        })
67    }
68
69    #[must_use]
70    pub fn part1(&self) -> u32 {
71        // Brent's algorithm for cycle detection, as used in 2017 day 6.
72        // The first repeated layout is the start of the cycle, and its bitmask is its rating.
73        let (mut power, mut lambda) = (1, 1);
74        let mut tortoise = self.initial;
75        let mut hare = Self::flat_step(self.initial);
76
77        while tortoise != hare {
78            if power == lambda {
79                tortoise = hare;
80                power *= 2;
81                lambda = 0;
82            }
83            hare = Self::flat_step(hare);
84            lambda += 1;
85        }
86
87        tortoise = self.initial;
88        hare = self.initial;
89        for _ in 0..lambda {
90            hare = Self::flat_step(hare);
91        }
92        while tortoise != hare {
93            tortoise = Self::flat_step(tortoise);
94            hare = Self::flat_step(hare);
95        }
96
97        tortoise
98    }
99
100    #[inline]
101    fn flat_step(bugs: u32) -> u32 {
102        let [ones, twos, fours] = bitwise_count4(&Self::cardinal_neighbors(bugs));
103        Self::next_layout(bugs, [ones, twos, fours, 0]) & GRID_MASK
104    }
105
106    #[inline]
107    fn cardinal_neighbors(current: u32) -> [u32; 4] {
108        let left = (current & !(COLUMN_MASK << (SIDE - 1))) << 1;
109        let right = (current & !COLUMN_MASK) >> 1;
110        let above = current << SIDE;
111        let below = current >> SIDE;
112        [left, right, above, below]
113    }
114
115    #[inline]
116    fn next_layout(current: u32, [ones, twos, fours, eights]: [u32; 4]) -> u32 {
117        let at_least_three = (ones & twos) | fours | eights;
118        let exactly_one = ones & !at_least_three;
119        let exactly_two = twos & !at_least_three;
120        exactly_one | (!current & exactly_two)
121    }
122
123    #[must_use]
124    pub fn part2(&self) -> u32 {
125        let mut first = [0; LEVELS];
126        let mut second = [0; LEVELS];
127        let (mut levels, mut next) = (&mut first, &mut second);
128
129        let level0 = REAL_MINUTES + 1;
130        let mut start = level0 - 1;
131        let mut end = level0 + 1;
132
133        levels[level0] = self.initial;
134
135        for _ in 0..self.recursive_minutes {
136            for (next_level, adjacent) in next[start..=end]
137                .iter_mut()
138                .zip(levels[start - 1..=end + 1].windows(3))
139            {
140                *next_level = Self::recursive_step(adjacent[0], adjacent[1], adjacent[2]);
141            }
142
143            start -= usize::from(next[start] != 0);
144            end += usize::from(next[end] != 0);
145            (levels, next) = (next, levels);
146        }
147
148        levels[start..=end]
149            .iter()
150            .copied()
151            .map(u32::count_ones)
152            .sum()
153    }
154
155    #[inline]
156    fn recursive_step(outer: u32, current: u32, inner: u32) -> u32 {
157        // Tile numbering:
158        //   0  1  2  3  4
159        //   5  6  7  8  9
160        //  10 11 12 13 14
161        //  15 16 17 18 19
162        //  20 21 22 23 24
163        // Current tile 12 holds the entire inner level
164        // Outer tile 12 holds the entire current level.
165
166        let [mut left, mut right, mut above, mut below] = Self::cardinal_neighbors(current);
167
168        // Every tile in this level's left column, right column, top row or bottom row gains a
169        // neighbour from a bug in tile 11, 13, 7 or 17 of outer.
170        left |= Self::bug_at(outer, MIDDLE - 1) * COLUMN_MASK;
171        right |= Self::bug_at(outer, MIDDLE + 1) * (COLUMN_MASK << (SIDE - 1));
172        above |= Self::bug_at(outer, MIDDLE - SIDE) * ROW_MASK;
173        below |= Self::bug_at(outer, MIDDLE + SIDE) * (ROW_MASK << (TILE_COUNT - SIDE));
174
175        // Tiles 7, 11, 13 and 17 gain up to 5 neighbours each from the outside edge of inner.
176        // The bits for these tiles in the above masks are always zero (as they are shifted from
177        // the middle which is always empty), which allows packing the 5 neighbours into the 4
178        // existing masks + 4 additional masks, so bitwise_count8 can be used.
179        let [l, r, a, b] = Self::inner_edge_neighbors(inner, 0);
180        left |= l;
181        right |= r;
182        above |= a;
183        below |= b;
184
185        let mut additional = [0; 4];
186        for (mask, i) in additional.iter_mut().zip(1..SIDE) {
187            let [l, r, a, b] = Self::inner_edge_neighbors(inner, i);
188            *mask = l | r | a | b;
189        }
190
191        // The middle tile holds the inner level, so it is never a bug
192        Self::next_layout(
193            current,
194            bitwise_count8(&[
195                left,
196                right,
197                above,
198                below,
199                additional[0],
200                additional[1],
201                additional[2],
202                additional[3],
203            ]),
204        ) & (GRID_MASK ^ (1 << MIDDLE))
205    }
206
207    // The [left, right, above, below] neighbours added by tile i of each of the inner level's edges
208    #[inline]
209    fn inner_edge_neighbors(inner: u32, i: usize) -> [u32; 4] {
210        [
211            Self::bug_at(inner, i * SIDE + SIDE - 1) << (MIDDLE + 1),
212            Self::bug_at(inner, i * SIDE) << (MIDDLE - 1),
213            Self::bug_at(inner, TILE_COUNT - SIDE + i) << (MIDDLE + SIDE),
214            Self::bug_at(inner, i) << (MIDDLE - SIDE),
215        ]
216    }
217
218    #[inline]
219    fn bug_at(bugs: u32, tile: usize) -> u32 {
220        (bugs >> tile) & 1
221    }
222}
223
224examples!(Day24 -> (u32, u32) [
225    {input: "....#\n#..#.\n#..##\n..#..\n#....", part1: 2_129_920, part2: 99},
226]);