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
use crate::input::Input;
pub fn solve(input: &mut Input) -> Result<u32, String> {
let (algorithm, image) = parse(input.text)?;
let enhancement_steps = input.part_values(2, 50);
let lit_pixels = algorithm.enhance(&image, enhancement_steps);
Ok(lit_pixels)
}
struct Image {
pixels: Vec<bool>,
}
impl Image {
fn new(pixels: Vec<bool>) -> Self {
Self { pixels }
}
fn size(&self) -> usize {
(self.pixels.len() as f64).sqrt() as usize
}
}
struct ImageEnhancementAlgorithm {
mappings: [bool; 512],
}
impl ImageEnhancementAlgorithm {
fn parse(text: &str) -> Self {
let mut mappings = [false; 512];
for (idx, value) in text.bytes().enumerate() {
mappings[idx] = value == b'#';
}
Self { mappings }
}
fn enhance(&self, image: &Image, steps: usize) -> u32 {
#![allow(clippy::unusual_byte_groupings)]
const fn is_pixel_lit(
pixels: &[bool],
prev_dim: usize,
image_size: usize,
x: usize,
y: isize,
eternity_val: bool,
) -> bool {
if x >= prev_dim || y < 0 || y >= (prev_dim as isize) {
eternity_val
} else {
pixels[x + (y as usize) * image_size]
}
}
let start_image_size = image.size();
let final_image_size = start_image_size + 2 * steps;
let mut current = vec![false; final_image_size * final_image_size];
let mut new = vec![false; final_image_size * final_image_size];
for (idx, &val) in image.pixels.iter().enumerate() {
let x = idx % start_image_size;
let y = idx / start_image_size;
current[x + y * final_image_size] = val;
}
let flashes_at_infinity = self.flashes_at_infinity();
for step in 1..=steps {
let infinity_is_lit = flashes_at_infinity && step % 2 == 0;
let round_size = start_image_size + 2 * (step - 1);
for y in 0..start_image_size + 2 * step {
let mut running_idx = if infinity_is_lit { 0b011_011_011 } else { 0 };
for x in 0..start_image_size + 2 * step {
let above = y as isize - 2;
let at = y as isize - 1;
let below = y as isize;
let new_column = (usize::from(is_pixel_lit(
¤t,
round_size,
final_image_size,
x,
above,
infinity_is_lit,
)) << 6)
| (usize::from(is_pixel_lit(
¤t,
round_size,
final_image_size,
x,
at,
infinity_is_lit,
)) << 3)
| usize::from(is_pixel_lit(
¤t,
round_size,
final_image_size,
x,
below,
infinity_is_lit,
));
running_idx = ((running_idx << 1) & 0b110_110_110) | new_column;
new[x + y * final_image_size] = self.mappings[running_idx];
}
}
std::mem::swap(&mut current, &mut new);
}
current.iter().filter(|&&b| b).count() as u32
}
const fn flashes_at_infinity(&self) -> bool {
self.mappings[0]
}
}
fn parse(text: &str) -> Result<(ImageEnhancementAlgorithm, Image), String> {
let (algo_string, image_string) = text.split_once("\n\n").ok_or("No two blocks of text")?;
if algo_string.len() != 512 {
return Err("Image enhancement algorithm is not 512 chars long".to_string());
}
let algo = ImageEnhancementAlgorithm::parse(algo_string);
if algo.mappings[0] && algo.mappings[511] {
return Err(
"Cannot have both first and last output pixels set in image enhancement algorithm"
.to_string(),
);
}
let image_height = image_string.lines().count();
let image_width = image_string
.lines()
.next()
.map(str::len)
.unwrap_or_default();
if image_height < 4 || image_width < 4 {
return Err("Too small image (4x4 is minimal size)".to_string());
}
if image_height != image_width {
return Err("Not a square image".to_string());
}
let mut image_pixels = vec![false; image_width * image_height];
for (y, line_str) in image_string.lines().enumerate() {
let line_bytes = line_str.bytes();
if line_bytes.len() != image_width {
return Err("Not all image rows have equal length".to_string());
}
for (x, b) in line_bytes.enumerate() {
if b == b'#' {
image_pixels[x + y * image_width] = true;
}
}
}
let image = Image::new(image_pixels);
Ok((algo, image))
}
#[test]
pub fn tests() {
use crate::input::{test_part_one, test_part_two};
let example = "..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#
#..#.
#....
##..#
..#..
..###";
test_part_one!(example => 35);
test_part_two!(example => 3351);
let real_input = include_str!("day20_input.txt");
test_part_one!(real_input => 4917);
test_part_two!(real_input => 16389);
}