aoc2023/16/src/main.rs
Keenan Tims 842def62dc
day16: back to elegant recursion now that we track beams
problem 1 solution was busting the stack, so used an iterative approach,
but now that we track lit beams we can bail out early enough not to bust
the stack. performance is the same but it's much nicer.
2023-12-15 23:54:22 -08:00

265 lines
7.1 KiB
Rust

use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::iter::repeat;
use std::time::Instant;
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
let start = Instant::now();
let ans1 = problem1(get_input());
let duration = start.elapsed();
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
let start = Instant::now();
let ans2 = problem2(get_input());
let duration = start.elapsed();
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
}
// PARSE
enum Interaction {
One(FromDirection),
Two((FromDirection, FromDirection)),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
enum FromDirection {
Left = 0,
Above = 1,
Right = 2,
Below = 3,
}
impl FromDirection {
fn mask(&self) -> u8 {
1 << *self as u8
}
// return the new pos for a ray that will be from the direction.
// a ray 'from' left 'goes' right
fn goes_pos(&self, pos: (i64, i64)) -> (i64, i64) {
match self {
Self::Left => (pos.0 + 1, pos.1),
Self::Right => (pos.0 - 1, pos.1),
Self::Above => (pos.0, pos.1 + 1),
Self::Below => (pos.0, pos.1 - 1),
}
}
fn reflect_ne(&self) -> Self {
match self {
Self::Left => Self::Below,
Self::Right => Self::Above,
Self::Above => Self::Right,
Self::Below => Self::Left,
}
}
fn opposite(&self) -> FromDirection {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Above => Self::Below,
Self::Below => Self::Above,
}
}
fn reflect_se(&self) -> FromDirection {
self.reflect_ne().opposite()
}
fn interact(&self, tile: char) -> Interaction {
match tile {
'.' => Interaction::One(*self),
'/' => Interaction::One(self.reflect_ne()),
'\\' => Interaction::One(self.reflect_se()),
'|' => match self {
FromDirection::Above | FromDirection::Below => Interaction::One(*self),
FromDirection::Left | FromDirection::Right => {
Interaction::Two((FromDirection::Above, FromDirection::Below))
}
},
'-' => match self {
FromDirection::Left | FromDirection::Right => Interaction::One(*self),
FromDirection::Above | FromDirection::Below => {
Interaction::Two((FromDirection::Left, FromDirection::Right))
}
},
c => unimplemented!("invalid tile {}", c),
}
}
}
impl Display for FromDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Above => f.write_str("above"),
Self::Below => f.write_str("below"),
Self::Left => f.write_str("left"),
Self::Right => f.write_str("right"),
}
}
}
struct Contraption {
tiles: Vec<Vec<char>>,
}
struct VisitState {
visited_from: Vec<Vec<u8>>,
}
impl VisitState {
fn visit(&mut self, pos: (i64, i64), dir: FromDirection) -> bool {
let pos_state = &mut self.visited_from[pos.1 as usize][pos.0 as usize];
if *pos_state & dir.mask() > 0 {
false
} else {
*pos_state |= dir.mask();
true
}
}
fn score(&self) -> u64 {
self.visited_from
.iter()
.flatten()
.filter(|c| **c != 0)
.count() as u64
}
#[allow(dead_code)]
fn dump(&self) {
println!("Score {}:", self.score());
for line in &self.visited_from {
println!(
" {}",
String::from_iter(line.iter().map(|b| if *b == 0 { '.' } else { '#' }))
);
}
}
}
impl Contraption {
fn height(&self) -> i64 {
self.tiles.len() as i64
}
fn width(&self) -> i64 {
self.tiles[0].len() as i64
}
fn cast_ray<'a>(&'a mut self, pos: (i64, i64), dir: FromDirection) -> VisitState {
let mut state = self.empty_state();
self.cast_ray_inner(&mut state, pos, dir);
state
}
fn cast_ray_inner<'a>(
&'a mut self,
state: &'a mut VisitState,
pos: (i64, i64),
dir: FromDirection,
) {
if pos.0 >= 0
&& pos.1 >= 0
&& pos.0 < self.width()
&& pos.1 < self.height()
&& state.visit(pos, dir)
{
match dir.interact(self.tiles[pos.1 as usize][pos.0 as usize]) {
Interaction::One(dir) => self.cast_ray_inner(state, dir.goes_pos(pos), dir),
Interaction::Two((dir1, dir2)) => {
self.cast_ray_inner(state, dir1.goes_pos(pos), dir1);
self.cast_ray_inner(state, dir2.goes_pos(pos), dir2);
}
};
}
}
fn empty_state(&self) -> VisitState {
let mut visited_from = Vec::new();
for _ in 0..self.height() {
visited_from.push(Vec::from_iter(repeat(0).take(self.width() as usize)));
}
VisitState { visited_from }
}
}
impl<T: BufRead> From<Lines<T>> for Contraption {
fn from(lines: Lines<T>) -> Self {
let mut tiles = Vec::new();
for line in lines {
tiles.push(line.unwrap().chars().map(|c| c.into()).collect());
}
Contraption { tiles }
}
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut contraption = Contraption::from(input);
contraption.cast_ray((0, 0), FromDirection::Left).score()
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let mut contraption = Contraption::from(input);
let rows_max = (0..contraption.height()).fold(0, |max_tiles, y| {
std::cmp::max(
max_tiles,
std::cmp::max(
contraption.cast_ray((0, y), FromDirection::Left).score(),
contraption
.cast_ray((contraption.width() - 1, y), FromDirection::Right)
.score(),
),
)
});
let cols_max = (0..contraption.width()).fold(0, |max_tiles, x| {
std::cmp::max(
max_tiles,
std::cmp::max(
contraption.cast_ray((x, 0), FromDirection::Above).score(),
contraption
.cast_ray((x, contraption.height() - 1), FromDirection::Below)
.score(),
),
)
});
std::cmp::max(rows_max, cols_max)
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &r".|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 46);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 51);
}
}