1use hashbrown::HashMap;
2use itertools::Itertools;
3use std::fs;
4
5type Img = HashMap<(i32, i32), bool>;
6type Key = (bool, bool, bool, bool, bool, bool, bool, bool, bool);
7type Lut = HashMap<Key, bool>;
8
9fn _key(int: usize) -> Key {
10 assert!(int < 512);
11 (
12 int & 0b100000000 != 0,
13 int & 0b010000000 != 0,
14 int & 0b001000000 != 0,
15 int & 0b000100000 != 0,
16 int & 0b000010000 != 0,
17 int & 0b000001000 != 0,
18 int & 0b000000100 != 0,
19 int & 0b000000010 != 0,
20 int & 0b000000001 != 0,
21 )
22}
23
24fn _pixel(ch: char) -> bool {
25 match ch {
26 '.' => false,
27 '#' => true,
28 _ => panic!("Unexpected char '{}'", ch),
29 }
30}
31fn _img(text: &str) -> Img {
32 let mut result = HashMap::new();
33 let mut lines = text.lines();
34 lines.next();
35 lines.next();
36 for (r, line) in lines.enumerate() {
37 for (c, cell) in line.chars().enumerate() {
38 result.insert((r as i32, c as i32), _pixel(cell));
39 }
40 }
41
42 result
43}
44
45fn _lut(text: &str) -> HashMap<Key, bool> {
46 let mut lines = text.lines();
47 let line = lines.next().unwrap();
48 line.chars()
49 .enumerate()
50 .map(|(i, ch)| (_key(i), _pixel(ch)))
51 .collect()
52}
53fn _once_enhanced(img: &Img, lut: &Lut, padding: bool, min: i32, max: i32) -> Img {
54 let mut result = HashMap::with_capacity((max - min + 3).pow(2) as usize);
55 for r in min - 1..=max + 1 {
56 for c in min - 1..=max + 1 {
57 let key: Key = (
58 *img.get(&(r - 1, c - 1)).unwrap_or(&padding),
59 *img.get(&(r - 1, c)).unwrap_or(&padding),
60 *img.get(&(r - 1, c + 1)).unwrap_or(&padding),
61 *img.get(&(r, c - 1)).unwrap_or(&padding),
62 *img.get(&(r, c)).unwrap_or(&padding),
63 *img.get(&(r, c + 1)).unwrap_or(&padding),
64 *img.get(&(r + 1, c - 1)).unwrap_or(&padding),
65 *img.get(&(r + 1, c)).unwrap_or(&padding),
66 *img.get(&(r + 1, c + 1)).unwrap_or(&padding),
67 );
68 result.insert((r, c), *lut.get(&key).unwrap());
69 }
70 }
71 result
72}
73
74fn _multi_enhanced(img: &Img, lut: &Lut, num_round: usize) -> Img {
75 let odd = *lut
76 .get(&(
77 false, false, false, false, false, false, false, false, false,
78 ))
79 .unwrap();
80 let even = match odd {
81 false => false,
82 true => *lut
83 .get(&(true, true, true, true, true, true, true, true, true))
84 .unwrap(),
85 };
86
87 let (min, max) = match img.keys().map(|k| k.0).minmax() {
88 itertools::MinMaxResult::NoElements => panic!("No elements"),
89 itertools::MinMaxResult::OneElement(only) => (only, only),
90 itertools::MinMaxResult::MinMax(min, max) => (min, max),
91 };
92
93 let mut result = _once_enhanced(img, lut, even, min, max);
94 for i in 1..num_round as i32 {
95 result = _once_enhanced(
96 &result,
97 lut,
98 match i % 2 {
99 0 => even,
100 1 => odd,
101 _ => panic!("Oups"),
102 },
103 min - i,
104 max + i,
105 );
106 }
107 result
108}
109
110pub fn part_1(input: &str) -> u64 {
111 let n = 2;
112 let img = _img(input);
113 let lut = _lut(input);
114 let enhanced = _multi_enhanced(&img, &lut, n);
115 enhanced.values().map(|b| *b as u64).sum()
116}
117
118pub fn part_2(input: &str) -> u64 {
119 let n = 50;
120 let img = _img(input);
121 let lut = _lut(input);
122 let enhanced = _multi_enhanced(&img, &lut, n);
123 enhanced.values().map(|b| *b as u64).sum()
124}
125
126fn _from_file<F, T>(func: F, stem: &str) -> T
127where
128 F: Fn(&str) -> T,
129{
130 func(&fs::read_to_string(format!("inputs/20/{}.txt", stem)).unwrap())
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn part_1_works_on_example() {
139 assert_eq!(_from_file(part_1, "example"), 35);
140 }
141
142 #[test]
143 fn part_1_works_on_input() {
144 assert_eq!(_from_file(part_1, "input"), 5571);
145 }
146
147 #[test]
148 fn part_2_works_on_example() {
149 assert_eq!(_from_file(part_2, "example"), 3351);
150 }
151
152 #[test]
153 fn part_2_works_on_input() {
154 assert_eq!(_from_file(part_2, "input"), 17965);
155 }
156}