day16: much cleaner, nicely factored solution, and much faster

This commit is contained in:
Keenan Tims 2023-12-15 23:42:43 -08:00
parent 332620db0f
commit 427a0c766b
Signed by: ktims
GPG Key ID: 11230674D69038D4

View File

@ -28,12 +28,72 @@ fn main() {
// PARSE // PARSE
enum Interaction {
One(FromDirection),
Two((FromDirection, FromDirection)),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
enum FromDirection { enum FromDirection {
Left, Left = 0,
Above, Above = 1,
Right, Right = 2,
Below, 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 { impl Display for FromDirection {
@ -52,44 +112,55 @@ struct Contraption {
} }
struct VisitState { struct VisitState {
energized: Vec<Vec<bool>>, visited_from: Vec<Vec<u8>>,
visited_rays: HashSet<((i64, i64), FromDirection)>,
} }
impl VisitState { impl VisitState {
fn score(&self) -> u64 { fn visit(&mut self, pos: (i64, i64), dir: FromDirection) -> bool {
self.energized.iter().flatten().filter(|c| **c).count() as u64 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) { fn dump(&self) {
println!("Score {}:", self.score()); println!("Score {}:", self.score());
for line in &self.energized { for line in &self.visited_from {
println!( println!(
" {}", " {}",
String::from_iter(line.iter().map(|b| if *b { '#' } else { '.' })) String::from_iter(line.iter().map(|b| if *b == 0 { '.' } else { '#' }))
); );
} }
} }
} }
impl Contraption { impl Contraption {
fn cast_ray<'a>( fn height(&self) -> i64 {
&'a mut self, self.tiles.len() as i64
state: &'a mut VisitState, }
pos: (i64, i64), fn width(&self) -> i64 {
dir: FromDirection, self.tiles[0].len() as i64
) { }
let mut new_rays = self.cast_ray_inner(state, pos, dir); fn cast_ray<'a>(&'a mut self, pos: (i64, i64), dir: FromDirection) -> VisitState {
let mut state = self.empty_state();
loop { let mut new_rays = self.cast_ray_inner(&mut state, pos, dir);
while new_rays.len() != 0 {
new_rays = new_rays new_rays = new_rays
.iter() .iter()
.flat_map(|(pos, dir)| self.cast_ray_inner(state, *pos, *dir)) .flat_map(|(pos, dir)| self.cast_ray_inner(&mut state, *pos, *dir))
.collect(); .collect();
if new_rays.len() == 0 {
break;
}
} }
state
} }
fn cast_ray_inner<'a>( fn cast_ray_inner<'a>(
&'a mut self, &'a mut self,
@ -97,68 +168,29 @@ impl Contraption {
mut pos: (i64, i64), mut pos: (i64, i64),
mut dir: FromDirection, mut dir: FromDirection,
) -> Vec<((i64, i64), FromDirection)> { ) -> Vec<((i64, i64), FromDirection)> {
let width = state.energized[0].len();
let height = state.energized.len();
let mut new_rays = Vec::new(); let mut new_rays = Vec::new();
while pos.0 >= 0 && pos.1 >= 0 && pos.0 < width as i64 && pos.1 < height as i64 {
// visit pos while pos.0 >= 0 && pos.1 >= 0 && pos.0 < self.width() && pos.1 < self.height() {
state.energized[pos.1 as usize][pos.0 as usize] = true; if !state.visit(pos, dir) {
if !state.visited_rays.insert((pos, dir)) {
break; break;
} }
(pos, dir) = match self.tiles[pos.1 as usize][pos.0 as usize] { dir = match dir.interact(self.tiles[pos.1 as usize][pos.0 as usize]) {
'.' => match dir { Interaction::One(dir) => dir,
FromDirection::Left => ((pos.0 + 1, pos.1), dir), Interaction::Two((dir1, dir2)) => {
FromDirection::Right => ((pos.0 - 1, pos.1), dir), new_rays.push((pos, dir1));
FromDirection::Above => ((pos.0, pos.1 + 1), dir), dir2
FromDirection::Below => ((pos.0, pos.1 - 1), dir), }
}, };
'/' => match dir { pos = dir.goes_pos(pos);
// from left, go up, from below
FromDirection::Left => ((pos.0, pos.1 - 1), FromDirection::Below),
// from up, go left, from the right
FromDirection::Above => ((pos.0 - 1, pos.1), FromDirection::Right),
// from right, go down, from above
FromDirection::Right => ((pos.0, pos.1 + 1), FromDirection::Above),
// from below, go right, from left
FromDirection::Below => ((pos.0 + 1, pos.1), FromDirection::Left),
},
'\\' => match dir {
FromDirection::Left => ((pos.0, pos.1 + 1), FromDirection::Above),
FromDirection::Above => ((pos.0 + 1, pos.1), FromDirection::Left),
FromDirection::Right => ((pos.0, pos.1 - 1), FromDirection::Below),
FromDirection::Below => ((pos.0 - 1, pos.1), FromDirection::Right),
},
'-' => match dir {
FromDirection::Left => ((pos.0 + 1, pos.1), dir),
FromDirection::Right => ((pos.0 - 1, pos.1), dir),
FromDirection::Above | FromDirection::Below => {
new_rays.push(((pos.0 + 1, pos.1), FromDirection::Left));
((pos.0 - 1, pos.1), FromDirection::Right)
}
},
'|' => match dir {
FromDirection::Above => ((pos.0, pos.1 + 1), dir),
FromDirection::Below => ((pos.0, pos.1 - 1), dir),
FromDirection::Left | FromDirection::Right => {
new_rays.push(((pos.0, pos.1 + 1), FromDirection::Above));
((pos.0, pos.1 - 1), FromDirection::Below)
}
},
c => unimplemented!("invalid character {}", c),
}
} }
new_rays new_rays
} }
fn empty_state(&self) -> VisitState { fn empty_state(&self) -> VisitState {
let mut energized = Vec::new(); let mut visited_from = Vec::new();
for _ in 0..self.tiles.len() { for _ in 0..self.height() {
energized.push(Vec::from_iter(repeat(false).take(self.tiles[0].len()))); visited_from.push(Vec::from_iter(repeat(0).take(self.width() as usize)));
}
VisitState {
energized,
visited_rays: HashSet::new(),
} }
VisitState { visited_from }
} }
} }
@ -176,48 +208,39 @@ impl<T: BufRead> From<Lines<T>> for Contraption {
fn problem1<T: BufRead>(input: Lines<T>) -> u64 { fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut contraption = Contraption::from(input); let mut contraption = Contraption::from(input);
let mut state = contraption.empty_state();
contraption.cast_ray(&mut state, (0,0), FromDirection::Left); contraption.cast_ray((0, 0), FromDirection::Left).score()
state.energized.iter().flatten().filter(|c| **c).count() as u64
} }
// PROBLEM 2 solution // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let mut contraption = Contraption::from(input); let mut contraption = Contraption::from(input);
let mut max_tiles = 0u64;
for y in 0..contraption.tiles.len() as i64 { let rows_max = (0..contraption.height()).fold(0, |max_tiles, y| {
let mut left_state = contraption.empty_state(); std::cmp::max(
contraption.cast_ray(&mut left_state, (0, y), FromDirection::Left);
let mut right_state = contraption.empty_state();
contraption.cast_ray(
&mut right_state,
(contraption.tiles[0].len() as i64 - 1, y),
FromDirection::Right,
);
max_tiles = std::cmp::max(
max_tiles, max_tiles,
std::cmp::max(left_state.score(), right_state.score()), std::cmp::max(
); contraption.cast_ray((0, y), FromDirection::Left).score(),
} contraption
for x in 0..contraption.tiles[0].len() as i64 { .cast_ray((contraption.width() - 1, y), FromDirection::Right)
let mut top_state = contraption.empty_state(); .score(),
contraption.cast_ray(&mut top_state, (x, 0), FromDirection::Above); ),
let mut bottom_state = contraption.empty_state(); )
contraption.cast_ray( });
&mut bottom_state,
(x, contraption.tiles.len() as i64 - 1),
FromDirection::Below,
);
max_tiles = std::cmp::max(
max_tiles,
std::cmp::max(top_state.score(), bottom_state.score()),
);
}
max_tiles 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)] #[cfg(test)]