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.
265 lines
7.1 KiB
Rust
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);
|
|
}
|
|
}
|