aoc/year2017/day21.rs
1//! # Fractal Art
2//!
3//! The image size starts at 3x3, growing exponentially to 18x18 after 5 generations and 2187x2187
4//! after 18 generations. The first insight to solving efficiently is realizing that we don't need
5//! to compute the entire image, instead only the *count* of each pattern is needed. Multiplying
6//! the count of each pattern by the number of set bits in each pattern gives the result.
7//!
8//! The second insight is that after 3 generations, the 9x9 image can be split into nine 3x3
9//! images that are independent of each other and the enhancement cycle can start over.
10//! Interestingly, most of the 3x3 patterns in the input are not needed, only the starting 3x3
11//! pattern and the six 2x2 to 3x3 patterns.
12//!
13//! Adding a few extra made up rules:
14//!
15//! ```none
16//! ##/#. => ###/#.#/###
17//! .#/.# => .#./###/.#.
18//! ../.. => #.#/.#./#.#
19//! ```
20//!
21//! then using the example:
22//!
23//! ```none
24//! .#. #..# ##.##. ###|.#.|##.
25//! ..# => .... => #..#.. => #.#|###|#..
26//! ### .... ...... ###|.#.|...
27//! #..# ##.##. ---+---+---
28//! #..#.. .#.|##.|##.
29//! ...... ###|#..|#..
30//! .#.|...|...
31//! ---+---+---
32//! ##.|##.|#.#
33//! #..|#..|.#.
34//! ...|...|#.#
35//! ```
36//!
37//! Splitting the 9x9 grid results in:
38//!
39//! ```none
40//! 1 x ### 2 x .#. 5 x ##. 1 x #.#
41//! # # ### #.. .#.
42//! ### .#. ... #.#
43//! ```
44//!
45//! The enhancement cycle can start again with each 3x3 image. This means that we only need to
46//! calculate 2 generations for the starting image and each 2x2 to 3x3 rule.
47struct Pattern {
48 three: u32,
49 four: u32,
50 six: u32,
51 nine: [usize; 9],
52}
53
54pub fn parse(input: &str) -> Vec<u32> {
55 // 2⁴ = 16 possible 2x2 patterns
56 let mut pattern_lookup = [0; 16];
57 let mut two_to_three = [[0; 9]; 16];
58 // 2⁹ = 512 possible 3x3 patterns
59 let mut three_to_four = [[0; 16]; 512];
60
61 // Starting pattern .#./..#/### => 010/001/111 => b010001111 => 143
62 let mut todo = vec![143];
63
64 for line in input.lines().map(str::as_bytes) {
65 // The ASCII code for "#" 35 is odd and the code for "." 46 is even
66 // so we can convert to a 1 or 0 bit using bitwise AND with 1.
67 let bit = |i: usize| line[i] & 1;
68
69 if line.len() == 20 {
70 // 2x2 to 3x3.
71 let indices = [0, 1, 3, 4];
72 let from = indices.map(bit);
73
74 let indices = [9, 10, 11, 13, 14, 15, 17, 18, 19];
75 let value = indices.map(bit);
76
77 let pattern = todo.len();
78 todo.push(to_index(&value));
79
80 for key in two_by_two_permutations(from) {
81 two_to_three[key] = value;
82 pattern_lookup[key] = pattern;
83 }
84 } else {
85 // 3x3 to 4x4.
86 let indices = [0, 1, 2, 4, 5, 6, 8, 9, 10];
87 let from = indices.map(bit);
88
89 let indices = [15, 16, 17, 18, 20, 21, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33];
90 let value = indices.map(bit);
91
92 for key in three_by_three_permutations(from) {
93 three_to_four[key] = value;
94 }
95 }
96 }
97
98 let patterns: Vec<_> = todo
99 .iter()
100 .map(|&index| {
101 // Lookup 4x4 pattern then map to 6x6.
102 let four = three_to_four[index];
103 let mut six = [0; 36];
104
105 for (src, dst) in [(0, 0), (2, 3), (8, 18), (10, 21)] {
106 let index = to_index(&[four[src], four[src + 1], four[src + 4], four[src + 5]]);
107 let replacement = two_to_three[index];
108 six[dst..dst + 3].copy_from_slice(&replacement[0..3]);
109 six[dst + 6..dst + 9].copy_from_slice(&replacement[3..6]);
110 six[dst + 12..dst + 15].copy_from_slice(&replacement[6..9]);
111 }
112
113 // Map 6x6 pattern to nine 3x3 patterns.
114 let nine = [0, 2, 4, 12, 14, 16, 24, 26, 28].map(|i| {
115 let index = to_index(&[six[i], six[i + 1], six[i + 6], six[i + 7]]);
116 pattern_lookup[index]
117 });
118
119 let three = index.count_ones();
120 let four = four.iter().sum::<u8>() as u32;
121 let six = six.iter().sum::<u8>() as u32;
122
123 Pattern { three, four, six, nine }
124 })
125 .collect();
126
127 let mut current = vec![0; patterns.len()];
128 let mut result = Vec::new();
129
130 // Begin with single starting pattern.
131 current[0] = 1;
132
133 // Calculate generations 0 to 20 inclusive.
134 for _ in 0..7 {
135 let mut three = 0;
136 let mut four = 0;
137 let mut six = 0;
138 let mut next = vec![0; patterns.len()];
139
140 for (count, pattern) in current.iter().zip(patterns.iter()) {
141 three += count * pattern.three;
142 four += count * pattern.four;
143 six += count * pattern.six;
144 // Each 6x6 grid splits into nine 3x3 grids.
145 pattern.nine.iter().for_each(|&i| next[i] += count);
146 }
147
148 result.push(three);
149 result.push(four);
150 result.push(six);
151 current = next;
152 }
153
154 result
155}
156
157pub fn part1(input: &[u32]) -> u32 {
158 input[5]
159}
160
161pub fn part2(input: &[u32]) -> u32 {
162 input[18]
163}
164
165/// Generate an array of the 8 possible transformations from rotating and flipping
166/// the 2x2 input.
167fn two_by_two_permutations(mut a: [u8; 4]) -> [usize; 8] {
168 let mut indices = [0; 8];
169
170 for (i, index) in indices.iter_mut().enumerate() {
171 // Convert pattern to binary to use as lookup index.
172 *index = to_index(&a);
173 // Rotate clockwise
174 // 0 1 => 2 0
175 // 2 3 3 1
176 a = [a[2], a[0], a[3], a[1]];
177 // Flip vertical
178 // 0 1 => 2 3
179 // 2 3 0 1
180 if i == 3 {
181 a = [a[2], a[3], a[0], a[1]];
182 }
183 }
184
185 indices
186}
187
188/// Generate an array of the 8 possible transformations from rotating and flipping
189/// the 3x3 input.
190fn three_by_three_permutations(mut a: [u8; 9]) -> [usize; 8] {
191 let mut indices = [0; 8];
192
193 for (i, index) in indices.iter_mut().enumerate() {
194 // Convert pattern to binary to use as lookup index.
195 *index = to_index(&a);
196 // Rotate clockwise
197 // 0 1 2 => 6 3 0
198 // 3 4 5 7 4 1
199 // 6 7 8 8 5 2
200 a = [a[6], a[3], a[0], a[7], a[4], a[1], a[8], a[5], a[2]];
201 // Flip vertical
202 // 0 1 2 => 6 7 8
203 // 3 4 5 3 4 5
204 // 6 7 8 0 1 2
205 if i == 3 {
206 a = [a[6], a[7], a[8], a[3], a[4], a[5], a[0], a[1], a[2]];
207 }
208 }
209
210 indices
211}
212
213/// Convert a pattern slice of ones and zeroes to a binary number.
214fn to_index(a: &[u8]) -> usize {
215 a.iter().fold(0, |acc, &n| (acc << 1) | n as usize)
216}