-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday_18.rs
213 lines (191 loc) · 5.07 KB
/
day_18.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
//! Advent of Code 2015: Day 18: Like a GIF For Your Yard
use std::fmt;
type Light = bool;
const LIGHT_ON: Light = true;
const LIGHT_OFF: Light = false;
/// Structure to old Game Of Life state.
///
/// DO. NOT. EVER. TURN. IT. TO. A. SINGLE. DIMENSION. ARRAY.
///
/// The performance loss is kinda as follows:
///
/// ```text
/// year_2015::day_18/year_2015::day_18_v1
/// time: [10.337 ms 10.350 ms 10.364 ms]
/// change: [+32.681% +32.909% +33.125%] (p = 0.00 < 0.05)
/// Performance has regressed.
/// year_2015::day_18/year_2015::day_18_v2
/// time: [10.357 ms 10.385 ms 10.414 ms]
/// change: [+33.193% +33.534% +33.954%] (p = 0.00 < 0.05)
/// Performance has regressed.
/// ```
///
struct GameOfLifeGrid {
data: Vec<Vec<Light>>,
size: usize,
alive_corners: bool,
}
impl GameOfLifeGrid {
fn alive_count(&self) -> u16 {
self
.data
.iter()
.map(|row| row.iter().filter(|c| **c == LIGHT_ON).count() as u16)
.sum::<u16>()
}
#[inline]
fn get(&self, row: usize, line: usize) -> Light {
self.data[row][line]
}
#[inline]
fn set(&mut self, row: usize, line: usize, value: Light) {
self.data[row][line] = value
}
/// Optimization trick here: we loop on range of (-1..+1) on both axis, except
/// we have to skip our current position, which is (at worst) nine `if` checks
/// where only one will be really useful.
///
/// To get the optimization, we just grab the value as we iterate, and account
/// for it when matching status/neibours next.
fn cell_will_be_alive(&self, row: usize, line: usize) -> bool {
let row_from = std::cmp::max(0, row as i32 - 1) as usize;
let line_from = std::cmp::max(0, line as i32 - 1) as usize;
let row_to = std::cmp::min(self.size - 1, row + 1) as usize;
let line_to = std::cmp::min(self.size - 1, line + 1) as usize;
let neighbors = (line_from..=line_to)
.map(|line_id| {
(row_from..=row_to)
.filter(|&row_id| self.get(row_id, line_id))
.count() as u8
})
.sum::<u8>();
let cell_status = self.get(row, line);
match (cell_status, neighbors) {
(LIGHT_ON, 3) => LIGHT_ON,
(LIGHT_ON, 4) => LIGHT_ON,
(LIGHT_OFF, 3) => LIGHT_ON,
_ => LIGHT_OFF,
}
}
fn revive_corners(&mut self) {
if !self.alive_corners {
return;
}
let max = self.size - 1;
for (row, line) in [
(0, 0),
(0, max),
(max, 0),
(max, max),
] {
self.set(row, line, LIGHT_ON)
}
}
fn mutate(&mut self) {
let data: Vec<Vec<bool>> = self
.data
.iter()
.enumerate()
.map(|(x, row)| {
row
.iter()
.enumerate()
.map(|(y, _cell)| self.cell_will_be_alive(x, y))
.collect()
})
.collect();
self.data = data;
self.revive_corners();
}
fn new(input: &str, alive_corners: bool) -> Self {
let mut data: Vec<Vec<Light>> = vec![];
for line in input.lines() {
let data_line: Vec<_> = line
.chars()
.map(|chr| match chr {
'#' => LIGHT_ON,
_ => LIGHT_OFF,
})
.collect();
data.push(data_line);
}
let size = data[0].len();
let mut grid = GameOfLifeGrid {
data,
size,
alive_corners,
};
grid.revive_corners();
grid
}
}
impl fmt::Display for GameOfLifeGrid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for row in 0..self.size {
for col in 0..self.size {
match self.data[row][col] {
LIGHT_ON => write!(f, "#")?,
LIGHT_OFF => write!(f, ".")?,
}
}
writeln!(f)?;
}
Ok(())
}
}
pub fn day_18_v1(input: impl Into<String>) -> u16 {
let mut grid = GameOfLifeGrid::new(&input.into(), false);
for _i in 0..100 {
grid.mutate();
}
grid.alive_count()
}
pub fn day_18_v2(input: impl Into<String>) -> u16 {
let mut grid = GameOfLifeGrid::new(&input.into(), true);
for _i in 0..100 {
grid.mutate();
}
grid.alive_count()
}
solvable!(day_18, day_18_v1, day_18_v2, u16);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn works_with_samples_v1() {
let sample = ".#.#.#\n...##.\n#....#\n..#...\n#.#..#\n####..";
let mut grid = GameOfLifeGrid::new(sample, false);
grid.mutate();
assert_eq!(grid.alive_count(), 11);
}
#[test]
fn works_with_samples_v2() {
let sample = "##.#.#\n...##.\n#....#\n..#...\n#.#..#\n####.#";
let mut grid = GameOfLifeGrid::new(sample, true);
grid.mutate();
assert_eq!(
grid.to_string(),
"#.##.#\n####.#\n...##.\n......\n#...#.\n#.####\n"
);
grid.mutate();
assert_eq!(
grid.to_string(),
"#..#.#\n#....#\n.#.##.\n...##.\n.#..##\n##.###\n"
);
grid.mutate();
assert_eq!(
grid.to_string(),
"#...##\n####.#\n..##.#\n......\n##....\n####.#\n"
);
grid.mutate();
assert_eq!(
grid.to_string(),
"#.####\n#....#\n...#..\n.##...\n#.....\n#.#..#\n"
);
grid.mutate();
assert_eq!(
grid.to_string(),
"##.###\n.##..#\n.##...\n.##...\n#.#...\n##...#\n"
);
}
}