day-16 done and dusted

This commit is contained in:
Dylan Thies
2023-12-16 15:02:56 -05:00
parent 694f6429a5
commit 9594ab22e6
6 changed files with 389 additions and 0 deletions

4
2023/day-16/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod part1;
pub use crate::part1::*;
pub mod part2;
pub use crate::part2::*;

12
2023/day-16/src/main.rs Normal file
View File

@@ -0,0 +1,12 @@
#![warn(clippy::all, clippy::pedantic)]
use day_16::part1;
use day_16::part2;
fn main() {
let input = include_str!("./input.txt");
let part1_result = part1(input);
println!("part 1: {part1_result}");
let part2_result = part2(input);
println!("part 2: {part2_result}");
}

162
2023/day-16/src/part1.rs Normal file
View File

@@ -0,0 +1,162 @@
#![warn(clippy::all, clippy::pedantic)]
use std::collections::{HashMap, HashSet, VecDeque};
use glam::IVec2;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete,
combinator::eof,
multi::{fold_many1, many1},
sequence::terminated,
IResult, Parser,
};
use nom_locate::LocatedSpan;
type Span<'a> = LocatedSpan<&'a str>;
type SpanIVec2<'a> = LocatedSpan<&'a str, IVec2>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Gadget {
Horizontal,
Vertical,
UlDr,
UrDl,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum FromDir {
Left,
Right,
Up,
Down,
}
/// day 16 part 1 of aoc 2023
///
/// # Arguments
/// - input the input for today's puzzle
///
/// # Panics
/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than
#[must_use]
pub fn part1(input: &str) -> String {
let input = Span::new(input);
let (_, (gadgets, maxes)) = parse_input(input).expect("always aoc");
let mut movement_cache = HashSet::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::from([(IVec2::new(0, 0), FromDir::Left)]);
while !queue.is_empty() {
let pos = queue.pop_front().unwrap();
if !movement_cache.insert(pos) {
continue; // cycle detection
}
let (pos, from) = pos;
if pos.x >= maxes.x || pos.x < 0 || pos.y >= maxes.y || pos.y < 0 {
continue; //outside grid
}
visited.insert(pos);
if let Some(gadget) = gadgets.get(&pos) {
match (gadget, from) {
(Gadget::Horizontal, FromDir::Left) => {
queue.push_back((pos + IVec2::new(1, 0), from));
}
(Gadget::Horizontal, FromDir::Right) => {
queue.push_back((pos + IVec2::new(-1, 0), from));
}
(Gadget::Horizontal, FromDir::Up | FromDir::Down) => {
queue.push_back((pos + IVec2::new(1, 0), FromDir::Left));
queue.push_back((pos + IVec2::new(-1, 0), FromDir::Right));
}
(Gadget::Vertical, FromDir::Up) => queue.push_back((pos + IVec2::new(0, 1), from)),
(Gadget::Vertical, FromDir::Down) => {
queue.push_back((pos + IVec2::new(0, -1), from));
}
(Gadget::Vertical, FromDir::Left | FromDir::Right) => {
queue.push_back((pos + IVec2::new(0, 1), FromDir::Up));
queue.push_back((pos + IVec2::new(0, -1), FromDir::Down));
}
(Gadget::UlDr, FromDir::Up) | (Gadget::UrDl, FromDir::Down) => {
queue.push_back((pos + IVec2::new(1, 0), FromDir::Left));
}
(Gadget::UlDr, FromDir::Down) | (Gadget::UrDl, FromDir::Up) => {
queue.push_back((pos + IVec2::new(-1, 0), FromDir::Right));
}
(Gadget::UlDr, FromDir::Left) | (Gadget::UrDl, FromDir::Right) => {
queue.push_back((pos + IVec2::new(0, 1), FromDir::Up));
}
(Gadget::UlDr, FromDir::Right) | (Gadget::UrDl, FromDir::Left) => {
queue.push_back((pos + IVec2::new(0, -1), FromDir::Down));
}
_ => unimplemented!("This should never happen"),
};
} else {
let next_pos = pos
+ match from {
FromDir::Left => IVec2::new(1, 0),
FromDir::Right => IVec2::new(-1, 0),
FromDir::Up => IVec2::new(0, 1),
FromDir::Down => IVec2::new(0, -1),
};
queue.push_back((next_pos, from));
}
}
visited.len().to_string()
}
fn with_xy(span: Span) -> SpanIVec2 {
let x = i32::try_from(span.get_column()).expect("overflow") - 1;
let y = i32::try_from(span.location_line()).expect("wrap around") - 1;
span.map_extra(|()| IVec2::new(x, y))
}
fn parse_gadget(input: Span) -> IResult<Span, (IVec2, Gadget)> {
alt((
tag("-").map(with_xy).map(|x| (x.extra, Gadget::Horizontal)),
tag("|").map(with_xy).map(|x| (x.extra, Gadget::Vertical)),
tag(r"\").map(with_xy).map(|x| (x.extra, Gadget::UlDr)),
tag(r"/").map(with_xy).map(|x| (x.extra, Gadget::UrDl)),
tag(".").map(with_xy).map(|x| (x.extra, Gadget::None)),
))(input)
}
fn parse_input(input: Span) -> IResult<Span, (HashMap<IVec2, Gadget>, IVec2)> {
let (input, (gadgets, max_x, max_y)) = fold_many1(
terminated(many1(parse_gadget), alt((complete::line_ending, eof))),
|| (HashMap::new(), 0, 0),
|(mut acc, _, max_y), row| {
let max_x = row.len().try_into().unwrap();
row.into_iter()
.filter(|(_, gadget)| *gadget != Gadget::None)
.for_each(|(pos, gadget)| {
acc.insert(pos, gadget);
});
(acc, max_x, max_y + 1)
},
)(input)?;
Ok((input, (gadgets, IVec2::new(max_x, max_y))))
}
#[cfg(test)]
mod test {
use super::*;
const INPUT: &str = r".|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....";
#[test]
fn part1_works() {
let result = part1(INPUT);
assert_eq!(result, "46".to_string());
}
}

187
2023/day-16/src/part2.rs Normal file
View File

@@ -0,0 +1,187 @@
#![warn(clippy::all, clippy::pedantic)]
use std::collections::{HashMap, HashSet, VecDeque};
use glam::IVec2;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete,
combinator::eof,
multi::{fold_many1, many1},
sequence::terminated,
IResult, Parser,
};
use nom_locate::LocatedSpan;
type Span<'a> = LocatedSpan<&'a str>;
type SpanIVec2<'a> = LocatedSpan<&'a str, IVec2>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Gadget {
Horizontal,
Vertical,
UlDr,
UrDl,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum FromDir {
Left,
Right,
Up,
Down,
}
/// day 16 part 2 of aoc 2023
///
/// # Arguments
/// - input the input for today's puzzle
///
/// # Panics
/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than
#[must_use]
pub fn part2(input: &str) -> String {
let input = Span::new(input);
let (_, (gadgets, maxes)) = parse_input(input).expect("always aoc");
(0..maxes.x)
.flat_map(|x| {
[
(IVec2::new(x, 0), FromDir::Up),
(IVec2::new(x, maxes.y - 1), FromDir::Down),
]
})
.chain((0..maxes.y).flat_map(|y| {
[
(IVec2::new(0, y), FromDir::Left),
(IVec2::new(maxes.x - 1, y), FromDir::Right),
]
}))
.map(|(start_pos, from)| check_from_start(start_pos, from, &gadgets, maxes))
.max()
.unwrap()
.to_string()
}
fn check_from_start(
start_pos: IVec2,
from: FromDir,
gadgets: &HashMap<IVec2, Gadget>,
maxes: IVec2,
) -> usize {
let mut movement_cache = HashSet::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::from([(start_pos, from)]);
while !queue.is_empty() {
let pos = queue.pop_front().unwrap();
if !movement_cache.insert(pos) {
continue; // cycle detection
}
let (pos, from) = pos;
if pos.x >= maxes.x || pos.x < 0 || pos.y >= maxes.y || pos.y < 0 {
continue; //outside grid
}
visited.insert(pos);
if let Some(gadget) = gadgets.get(&pos) {
match (gadget, from) {
(Gadget::Horizontal, FromDir::Left) => {
queue.push_back((pos + IVec2::new(1, 0), from));
}
(Gadget::Horizontal, FromDir::Right) => {
queue.push_back((pos + IVec2::new(-1, 0), from));
}
(Gadget::Horizontal, FromDir::Up | FromDir::Down) => {
queue.push_back((pos + IVec2::new(1, 0), FromDir::Left));
queue.push_back((pos + IVec2::new(-1, 0), FromDir::Right));
}
(Gadget::Vertical, FromDir::Up) => queue.push_back((pos + IVec2::new(0, 1), from)),
(Gadget::Vertical, FromDir::Down) => {
queue.push_back((pos + IVec2::new(0, -1), from));
}
(Gadget::Vertical, FromDir::Left | FromDir::Right) => {
queue.push_back((pos + IVec2::new(0, 1), FromDir::Up));
queue.push_back((pos + IVec2::new(0, -1), FromDir::Down));
}
(Gadget::UlDr, FromDir::Up) | (Gadget::UrDl, FromDir::Down) => {
queue.push_back((pos + IVec2::new(1, 0), FromDir::Left));
}
(Gadget::UlDr, FromDir::Down) | (Gadget::UrDl, FromDir::Up) => {
queue.push_back((pos + IVec2::new(-1, 0), FromDir::Right));
}
(Gadget::UlDr, FromDir::Left) | (Gadget::UrDl, FromDir::Right) => {
queue.push_back((pos + IVec2::new(0, 1), FromDir::Up));
}
(Gadget::UlDr, FromDir::Right) | (Gadget::UrDl, FromDir::Left) => {
queue.push_back((pos + IVec2::new(0, -1), FromDir::Down));
}
_ => unimplemented!("This should never happen"),
};
} else {
let next_pos = pos
+ match from {
FromDir::Left => IVec2::new(1, 0),
FromDir::Right => IVec2::new(-1, 0),
FromDir::Up => IVec2::new(0, 1),
FromDir::Down => IVec2::new(0, -1),
};
queue.push_back((next_pos, from));
}
}
visited.len()
}
fn with_xy(span: Span) -> SpanIVec2 {
let x = i32::try_from(span.get_column()).expect("overflow") - 1;
let y = i32::try_from(span.location_line()).expect("wrap around") - 1;
span.map_extra(|()| IVec2::new(x, y))
}
fn parse_gadget(input: Span) -> IResult<Span, (IVec2, Gadget)> {
alt((
tag("-").map(with_xy).map(|x| (x.extra, Gadget::Horizontal)),
tag("|").map(with_xy).map(|x| (x.extra, Gadget::Vertical)),
tag(r"\").map(with_xy).map(|x| (x.extra, Gadget::UlDr)),
tag(r"/").map(with_xy).map(|x| (x.extra, Gadget::UrDl)),
tag(".").map(with_xy).map(|x| (x.extra, Gadget::None)),
))(input)
}
fn parse_input(input: Span) -> IResult<Span, (HashMap<IVec2, Gadget>, IVec2)> {
let (input, (gadgets, max_x, max_y)) = fold_many1(
terminated(many1(parse_gadget), alt((complete::line_ending, eof))),
|| (HashMap::new(), 0, 0),
|(mut acc, _, max_y), row| {
let max_x = row.len().try_into().unwrap();
row.into_iter()
.filter(|(_, gadget)| *gadget != Gadget::None)
.for_each(|(pos, gadget)| {
acc.insert(pos, gadget);
});
(acc, max_x, max_y + 1)
},
)(input)?;
Ok((input, (gadgets, IVec2::new(max_x, max_y))))
}
#[cfg(test)]
mod test {
use super::*;
const INPUT: &str = r".|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....";
#[test]
fn part2_works() {
let result = part2(INPUT);
assert_eq!(result, "51".to_string());
}
}