aoc/year2015/
day06.rs

1//! # Probably a Fire Hazard
2//!
3//! This problem is easy to brute force but more challenging to solve efficiently.
4//!
5//! To trick to speed things up is to consider rectangles that have the same instructions instead of
6//! calculating point by point. Then for each rectangle we apply the instructions only once and
7//! multiply by its area.
8//!
9//! For example say there is only a single instruction `turn on 300,300 through 700,500`. This
10//! looks a little like:
11//!
12//! ```none
13//!     (0,0)
14//!     ┌────────────┐
15//!     │            │
16//!     │   ┌────┐   │
17//!     │   │    │   │
18//!     │   └────┘   │
19//!     │            │
20//!     └────────────┘(1000,1000)
21//! ```
22//!
23//! First we compute the x and y intervals:
24//!
25//! ```none
26//!     x: [0, 300, 701, 1000]
27//!     y: [0, 300, 501, 1000]
28//! ```
29//!
30//! The intervals are *inclusive*, so the interval after the instruction starts 1 higher. Next we
31//! break the grid into 3 x 3 = 9 rectangles, much fewer than the 1,000,000 individual elements.
32//!
33//! ```none
34//!     ┌───────────┐
35//!     │ A | B | C │
36//!     │...┌───┐...│
37//!     │ D │ E │ F │
38//!     │...└───┘...│
39//!     │ G | H | I │
40//!     └───────────┘
41//! ```
42//!
43//! For each of these rectangles we store a boolean if the rectangle to the left or above crosses an
44//! instruction boundary.
45//!
46//! ```none
47//!     Left             Up
48//!     ┌───────────┐    ┌───────────┐
49//!     │ T | F | F │    │ T | T | T │
50//!     │...┌───┐...│    │...┌───┐...│
51//!     │ T │ T │ T │    │ F │ T │ F │
52//!     │...└───┘...│    │...└───┘...│
53//!     │ T | F | F │    │ F | T | F │
54//!     └───────────┘    └───────────┘
55//! ```
56//!
57//! If there is no boundary then we can re-use the value either from the rectangle to the left or
58//! above. For example `D` is the same as `A`, `B` is also the same as `A` and `I` is the same as
59//! both `F` and `H`. This further reduces the different instruction sets to compute.
60//!
61//! For my input, there was ~100,000 rectangles but only ~20,000 different instructions regions
62//! needed to be computed. This is a 50x reduction from looking at each light individually.
63use crate::util::iter::*;
64use crate::util::parse::*;
65
66enum Command {
67    On,
68    Off,
69    Toggle,
70}
71
72impl Command {
73    fn from(bytes: &[u8]) -> Command {
74        match bytes[6] {
75            b'n' => Command::On,
76            b'f' => Command::Off,
77            b' ' => Command::Toggle,
78            _ => unreachable!(),
79        }
80    }
81}
82
83struct Rectangle {
84    x1: u32,
85    x2: u32,
86    y1: u32,
87    y2: u32,
88}
89
90impl Rectangle {
91    fn from([x1, y1, x2, y2]: [u32; 4]) -> Rectangle {
92        Rectangle { x1, x2, y1, y2 }
93    }
94
95    fn contains(&self, x: u32, y: u32) -> bool {
96        self.x1 <= x && x <= self.x2 && self.y1 <= y && y <= self.y2
97    }
98}
99
100struct Instruction {
101    command: Command,
102    rectangle: Rectangle,
103}
104
105impl Instruction {
106    fn from((bytes, points): (&[u8], [u32; 4])) -> Instruction {
107        let command = Command::from(bytes);
108        let rectangle = Rectangle::from(points);
109        Instruction { command, rectangle }
110    }
111}
112
113pub fn parse(input: &str) -> (u32, u32) {
114    let first = input.lines().map(str::as_bytes);
115    let second = input.iter_unsigned().chunk::<4>();
116    let instructions: Vec<_> = first.zip(second).map(Instruction::from).collect();
117
118    let mut xs = vec![0, 1000];
119    let mut ys = vec![0, 1000];
120
121    for instruction in &instructions {
122        let Rectangle { x1, x2, y1, y2 } = instruction.rectangle;
123        xs.push(x1);
124        xs.push(x2 + 1);
125        ys.push(y1);
126        ys.push(y2 + 1);
127    }
128
129    xs.sort_unstable();
130    xs.dedup();
131    ys.sort_unstable();
132    ys.dedup();
133
134    let mut x_index_from = [0; 1001];
135    for (i, &x) in xs.iter().enumerate() {
136        x_index_from[x as usize] = i;
137    }
138
139    let mut y_index_from = [0; 1001];
140    for (i, &y) in ys.iter().enumerate() {
141        y_index_from[y as usize] = i;
142    }
143
144    let stride = xs.len();
145    let capacity = stride * ys.len();
146    let mut up = vec![false; capacity];
147    let mut left = vec![false; capacity];
148    let mut previous = vec![(false, 0); capacity];
149
150    for instruction in &instructions {
151        let Rectangle { x1, x2, y1, y2 } = instruction.rectangle;
152        let x1 = x_index_from[x1 as usize];
153        let x2 = x_index_from[(x2 + 1) as usize];
154        let y1 = y_index_from[y1 as usize];
155        let y2 = y_index_from[(y2 + 1) as usize];
156
157        for x in x1..(x2 + 1) {
158            up[stride * y1 + x] = true;
159            up[stride * y2 + x] = true;
160        }
161        for y in y1..(y2 + 1) {
162            left[stride * y + x1] = true;
163            left[stride * y + x2] = true;
164        }
165    }
166
167    let mut result1 = 0;
168    let mut result2 = 0;
169
170    for j in 0..ys.len() - 1 {
171        let y1 = ys[j];
172        let y2 = ys[j + 1];
173
174        for i in 0..xs.len() - 1 {
175            let x1 = xs[i];
176            let x2 = xs[i + 1];
177            let area = (x2 - x1) * (y2 - y1);
178            let index = stride * j + i;
179
180            let current = if i > 0 && !left[index] {
181                previous[index - 1]
182            } else if j > 0 && !up[index] {
183                previous[index - stride]
184            } else {
185                let mut light = false;
186                let mut brightness: u8 = 0;
187
188                for instruction in &instructions {
189                    if instruction.rectangle.contains(x1, y1) {
190                        match instruction.command {
191                            Command::On => {
192                                light = true;
193                                brightness += 1;
194                            }
195                            Command::Off => {
196                                light = false;
197                                brightness = brightness.saturating_sub(1);
198                            }
199                            Command::Toggle => {
200                                light = !light;
201                                brightness += 2;
202                            }
203                        }
204                    }
205                }
206
207                (light, brightness)
208            };
209
210            previous[index] = current;
211            if current.0 {
212                result1 += area;
213            }
214            result2 += current.1 as u32 * area;
215        }
216    }
217
218    (result1, result2)
219}
220
221pub fn part1(input: &(u32, u32)) -> u32 {
222    input.0
223}
224
225pub fn part2(input: &(u32, u32)) -> u32 {
226    input.1
227}