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