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