Skip to content

Commit 8716492

Browse files
committed
Year 2017 Day 21
1 parent 32ff6cb commit 8716492

File tree

7 files changed

+231
-0
lines changed

7 files changed

+231
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
256256
| 18 | [Duet](https://adventofcode.com/2017/day/18) | [Source](src/year2017/day18.rs) | 7 |
257257
| 19 | [A Series of Tubes](https://adventofcode.com/2017/day/19) | [Source](src/year2017/day19.rs) | 20 |
258258
| 20 | [Particle Swarm](https://adventofcode.com/2017/day/20) | [Source](src/year2017/day20.rs) | 234 |
259+
| 21 | [Fractal Art](https://adventofcode.com/2017/day/21) | [Source](src/year2017/day21.rs) | 4 |
259260
| 23 | [Coprocessor Conflagration](https://adventofcode.com/2017/day/23) | [Source](src/year2017/day23.rs) | 55 |
260261
| 24 | [Electromagnetic Moat](https://adventofcode.com/2017/day/24) | [Source](src/year2017/day24.rs) | 280 |
261262

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ mod year2017 {
121121
benchmark!(year2017, day18);
122122
benchmark!(year2017, day19);
123123
benchmark!(year2017, day20);
124+
benchmark!(year2017, day21);
124125
benchmark!(year2017, day23);
125126
benchmark!(year2017, day24);
126127
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub mod year2017 {
103103
pub mod day18;
104104
pub mod day19;
105105
pub mod day20;
106+
pub mod day21;
106107
pub mod day23;
107108
pub mod day24;
108109
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ fn year2017() -> Vec<Solution> {
168168
solution!(year2017, day18),
169169
solution!(year2017, day19),
170170
solution!(year2017, day20),
171+
solution!(year2017, day21),
171172
solution!(year2017, day23),
172173
solution!(year2017, day24),
173174
]

src/year2017/day21.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ mod year2017 {
105105
mod day18_test;
106106
mod day19_test;
107107
mod day20_test;
108+
mod day21_test;
108109
mod day23_test;
109110
mod day24_test;
110111
}

tests/year2017/day21_test.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[test]
2+
fn part1_test() {
3+
// No example data
4+
}
5+
6+
#[test]
7+
fn part2_test() {
8+
// No example data
9+
}

0 commit comments

Comments
 (0)