1use std::array;
2use utils::parser::{ParseError, ParseResult};
3use utils::prelude::*;
4
5#[derive(Clone, Debug)]
11pub struct Day21 {
12 part1: u32,
13 part2: u32,
14}
15
16#[derive(Clone, Debug)]
17enum Rule {
18 TwoToThree(usize, usize),
19 ThreeToFour(usize, usize),
20}
21
22const START_3X3: usize = 0b010_001_111;
23
24impl Day21 {
25 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
26 let parse_iterator = parser::one_of((
27 Self::parse_square::<3>
28 .with_suffix(" => ")
29 .then(Self::parse_square::<4>)
30 .map(|(a, b)| Rule::ThreeToFour(a, b)),
31 Self::parse_square::<2>
32 .with_suffix(" => ")
33 .then(Self::parse_square::<3>)
34 .map(|(a, b)| Rule::TwoToThree(a, b)),
35 ))
36 .with_suffix(parser::eol())
37 .parse_iterator(input);
38
39 let mut rules_2x2_to_3x3 = [0usize; 16];
40 let mut rules_3x3_to_4x4 = [0usize; 512];
41 for item in parse_iterator {
42 match item? {
43 Rule::TwoToThree(two, three) => {
44 for transformed in Self::transformations_2x2(two) {
45 rules_2x2_to_3x3[transformed] = three;
46 }
47 }
48 Rule::ThreeToFour(three, four) => {
49 for transformed in Self::transformations_3x3(three) {
50 rules_3x3_to_4x4[transformed] = four;
51 }
52 }
53 }
54 }
55
56 let (mut three_counts, mut three_counts_next) = ([0; 512], [0; 512]);
57 three_counts[START_3X3] = 1;
58
59 let mut on_pixels = [0; 21];
60 for i in (0..=18).step_by(3) {
61 for (three, &count) in three_counts.iter().enumerate().filter(|&(_, &c)| c > 0) {
62 on_pixels[i] += three.count_ones() * count;
63
64 let four = rules_3x3_to_4x4[three];
65 on_pixels[i + 1] += four.count_ones() * count;
66
67 let six = Self::combine_3x3_into_6x6(
68 Self::split_4x4_into_2x2(four).map(|two| rules_2x2_to_3x3[two]),
69 );
70 on_pixels[i + 2] += six.count_ones() * count;
71
72 for two in Self::split_6x6_into_2x2(six) {
73 let next = rules_2x2_to_3x3[two];
74 three_counts_next[next] += count;
75 }
76 }
77
78 three_counts = three_counts_next;
79 three_counts_next.fill(0);
80 }
81
82 Ok(Self {
83 part1: on_pixels[5],
84 part2: on_pixels[18],
85 })
86 }
87
88 #[must_use]
89 pub fn part1(&self) -> u32 {
90 self.part1
91 }
92
93 #[must_use]
94 pub fn part2(&self) -> u32 {
95 self.part2
96 }
97
98 #[inline]
99 fn parse_square<const N: usize>(mut input: &[u8]) -> ParseResult<usize> {
100 let mut result = 0;
101 for row in 0..N {
102 for col in 0..N {
103 match input.first() {
104 Some(b'.') => {}
105 Some(b'#') => result |= 1 << (col + N * row),
106 _ => return Err((ParseError::Custom("expected '.' or '#'"), input)),
107 }
108 input = &input[1..];
109 }
110
111 if row < N - 1 {
112 if input.first().copied() != Some(b'/') {
113 return Err((ParseError::Custom("expected '/'"), input));
114 }
115 input = &input[1..];
116 }
117 }
118
119 Ok((result, input))
120 }
121
122 #[inline]
123 #[expect(clippy::identity_op)]
124 fn transformations_2x2(square: usize) -> [usize; 4] {
125 let b: [_; 4] = array::from_fn(|i| (square >> i) & 1);
126
127 [
128 square,
132 (b[2] << 0) | (b[0] << 1) | (b[3] << 2) | (b[1] << 3),
133 (b[3] << 0) | (b[2] << 1) | (b[1] << 2) | (b[0] << 3),
134 (b[1] << 0) | (b[3] << 1) | (b[0] << 2) | (b[2] << 3),
135 ]
136 }
137
138 #[inline]
139 #[rustfmt::skip]
140 #[expect(clippy::identity_op)]
141 fn transformations_3x3(square: usize) -> [usize; 8] {
142 let b: [_; 9] = array::from_fn(|i| (square >> i) & 1);
143
144 [
145 square,
150 (b[6] << 0) | (b[3] << 1) | (b[0] << 2) | (b[7] << 3) | (b[4] << 4) | (b[1] << 5) | (b[8] << 6) | (b[5] << 7) | (b[2] << 8),
151 (b[8] << 0) | (b[7] << 1) | (b[6] << 2) | (b[5] << 3) | (b[4] << 4) | (b[3] << 5) | (b[2] << 6) | (b[1] << 7) | (b[0] << 8),
152 (b[2] << 0) | (b[5] << 1) | (b[8] << 2) | (b[1] << 3) | (b[4] << 4) | (b[7] << 5) | (b[0] << 6) | (b[3] << 7) | (b[6] << 8),
153 (b[6] << 0) | (b[7] << 1) | (b[8] << 2) | (b[3] << 3) | (b[4] << 4) | (b[5] << 5) | (b[0] << 6) | (b[1] << 7) | (b[2] << 8),
158 (b[0] << 0) | (b[3] << 1) | (b[6] << 2) | (b[1] << 3) | (b[4] << 4) | (b[7] << 5) | (b[2] << 6) | (b[5] << 7) | (b[8] << 8),
159 (b[2] << 0) | (b[1] << 1) | (b[0] << 2) | (b[5] << 3) | (b[4] << 4) | (b[3] << 5) | (b[8] << 6) | (b[7] << 7) | (b[6] << 8),
160 (b[8] << 0) | (b[5] << 1) | (b[2] << 2) | (b[7] << 3) | (b[4] << 4) | (b[1] << 5) | (b[6] << 6) | (b[3] << 7) | (b[0] << 8),
161 ]
162 }
163
164 #[inline]
165 fn split_4x4_into_2x2(square: usize) -> [usize; 4] {
166 [0, 2, 8, 10].map(|i| ((square >> i) & 0b11) | (((square >> (i + 4)) & 0b11) << 2))
171 }
172
173 #[inline]
174 fn combine_3x3_into_6x6(squares: [usize; 4]) -> usize {
175 let mut result = 0;
182 for (&square, offset) in squares.iter().zip([0, 3, 18, 21]) {
183 result |= ((square & 0b111) << offset)
184 | (((square >> 3) & 0b111) << (offset + 6))
185 | (((square >> 6) & 0b111) << (offset + 12));
186 }
187 result
188 }
189
190 #[inline]
191 fn split_6x6_into_2x2(square: usize) -> [usize; 9] {
192 [0, 2, 4, 12, 14, 16, 24, 26, 28]
199 .map(|i| ((square >> i) & 0b11) | (((square >> (i + 6)) & 0b11) << 2))
200 }
201}
202
203examples!(Day21 -> (u32, u32) []);