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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
/* Copyright 2022-2023 Mario Finelli
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Advent of Code 2015 Day 6: <https://adventofcode.com/2015/day/6>
//!
//! This challenge is pretty straightforward and amounts to dealing with a big
//! grid of booleans in part one or a big grid of individual brightnesses
//! (integers) in part two.
use std::collections::HashMap;
/// Instruction is a representation of the kind of operation to take: toggle
/// the state of the light, turn if on (even if it's already on), or turn it
/// off (even if it's already off). In part one the case of turning on or off
/// a light that is already on or off this essentially results in a no-op. In
/// part two in which we track total brightness turn off if the value is
/// already zero results in a no-op.
#[derive(Debug, PartialEq)]
enum Instruction {
Toggle,
TurnOn,
TurnOff,
}
/// The solution for part one of the day six challenge.
///
/// Given the input as a string we start by building a `1000x1000` grid of
/// booleans that represent the state of each light in each position. All of
/// the lights start "off" (`false`). We then parse each instruction and loop
/// through the lights in the instruction, turning them on or off as necessary.
/// Finally, we make one final pass through every light and increment our final
/// counter if the light is on and then return that final sum.
///
/// # Example
/// ```rust
/// # use aoc::y15d06::y15d06p1;
/// // probably read this from the input file...
/// let input = concat![
/// "turn on 12,12 through 40,40\n",
/// "turn off 10,10 through 23,23\n",
/// "turn on 15,14 through 16,15\n",
/// "toggle 20,21 through 22,21\n",
/// ];
/// assert_eq!(y15d06p1(input), 704);
/// ```
pub fn y15d06p1(input: &str) -> u32 {
let lines: Vec<_> = input.lines().collect();
let mut on = 0;
let mut lights = [[false; 1000]; 1000];
for line in lines {
let (instruction, x1, y1, x2, y2) = parse_instruction(line);
for light_row in lights.iter_mut().take(y2 + 1).skip(y1) {
for light in light_row.iter_mut().take(x2 + 1).skip(x1) {
if instruction == Instruction::Toggle {
*light = !(*light);
} else {
*light = instruction != Instruction::TurnOff;
}
}
}
}
for light_row in &lights {
for light in light_row.iter() {
if *light {
on += 1;
}
}
}
on
}
/// The solution part two of the day six challenge.
///
/// Part two is both similar and dissimilar to part one. At first I wanted to
/// manage the brightness count in a two-dimensional array of `u32`s but I
/// kept overflowing the stack so instead we maintain a
/// [`std::collections::HashMap`] of the brightness of each lights coordinates.
/// If the lights current brightness is zero (or would result in zero after the
/// operation) it does not have an entry in the map (or has its entry removed).
///
/// So, given our input as a string like in part one, we start by parsing each
/// instruction and then loop through the light positions defined by the
/// instruction. As mentioned in the prompt "turn on" means increase the
/// brightness by one, "turn off" means decrease it by one (but not below
/// zero), and "toggle" means increase the brightness by two. As we loop
/// through each coordinate we create missing entries (lights that currently
/// have a brightness of zero) and remove entries (lights whose brightness
/// would become zero) as necessary. Finally, to compute the total brightness
/// we just need to add the value of all of the lights that we're currently
/// tracking (i.e., have a brightness of at least one).
///
/// # Example
/// ```rust
/// # use aoc::y15d06::y15d06p2;
/// // probably read this from the input file...
/// let input = concat![
/// "turn on 102,12 through 400,40\n",
/// "turn off 100,10 through 203,23\n",
/// "turn on 15,14 through 16,15\n",
/// "toggle 20,21 through 22,21",
/// ];
/// assert_eq!(y15d06p2(input), 7457);
/// ```
pub fn y15d06p2(input: &str) -> u64 {
let lines: Vec<_> = input.lines().collect();
let mut lights = HashMap::new();
let mut total = 0;
for line in lines {
let (instruction, x1, y1, x2, y2) = parse_instruction(line);
for y in y1..y2 + 1 {
for x in x1..x2 + 1 {
if instruction == Instruction::Toggle {
lights.entry((x, y)).and_modify(|v| *v += 2).or_insert(2);
} else if instruction == Instruction::TurnOff {
let v = lights.entry((x, y)).or_insert(0);
if *v == 0 {
lights.remove(&(x, y));
} else {
*v -= 1;
}
} else {
lights.entry((x, y)).and_modify(|v| *v += 1).or_insert(1);
}
}
}
}
for value in lights.values() {
total += value;
}
total
}
/// This function simply parses an input line and returns the necessary
/// instruction with the matching coordinates because the positions of the
/// coordinates change if the string starts with "toggle" or "turn on/off" by
/// one.
fn parse_instruction(line: &str) -> (Instruction, usize, usize, usize, usize) {
let parts: Vec<_> = line.split_whitespace().collect();
if line.starts_with("toggle") {
let from: Vec<_> = parts[1].split(',').collect();
let to: Vec<_> = parts[3].split(',').collect();
let x1: usize = from[0].parse().unwrap();
let x2: usize = to[0].parse().unwrap();
let y1: usize = from[1].parse().unwrap();
let y2: usize = to[1].parse().unwrap();
(Instruction::Toggle, x1, y1, x2, y2)
} else {
let from: Vec<_> = parts[2].split(',').collect();
let to: Vec<_> = parts[4].split(',').collect();
let x1: usize = from[0].parse().unwrap();
let x2: usize = to[0].parse().unwrap();
let y1: usize = from[1].parse().unwrap();
let y2: usize = to[1].parse().unwrap();
if line.starts_with("turn off") {
(Instruction::TurnOff, x1, y1, x2, y2)
} else {
(Instruction::TurnOn, x1, y1, x2, y2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_parse_instruction() {
let mut l = "toggle 1,2 through 10,20";
assert_eq!(parse_instruction(l), (Instruction::Toggle, 1, 2, 10, 20));
l = "turn on 3,4 through 30,40";
assert_eq!(parse_instruction(l), (Instruction::TurnOn, 3, 4, 30, 40));
l = "turn off 5,6 through 50,60";
assert_eq!(parse_instruction(l), (Instruction::TurnOff, 5, 6, 50, 60));
}
#[test]
fn it_works() {
let mut input = "turn on 0,0 through 999,999";
assert_eq!(y15d06p1(input), 1000000);
input = "turn on 0,0 through 0,99\ntoggle 0,0 through 0,999\n";
assert_eq!(y15d06p1(input), 900);
input = concat!(
"turn on 0,0 through 999,999\n",
"toggle 0,0 through 999,0\n",
"turn off 499,499 through 500,500\n"
);
assert_eq!(y15d06p1(input), 998996);
input = "turn on 0,0 through 0,0\ntoggle 0,0 through 999,999";
assert_eq!(y15d06p2(input), 2000001);
}
#[test]
#[ignore]
fn the_solution() {
let contents = fs::read_to_string("input/2015/day06.txt").unwrap();
assert_eq!(y15d06p1(&contents), 543903);
assert_eq!(y15d06p2(&contents), 14687245);
}
}