day-16 done and dusted
This commit is contained in:
10
2023/Cargo.lock
generated
10
2023/Cargo.lock
generated
@@ -152,6 +152,16 @@ dependencies = [
|
|||||||
"rstest",
|
"rstest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "day-16"
|
||||||
|
version = "2023.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
"itertools",
|
||||||
|
"nom",
|
||||||
|
"nom_locate",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "day-2"
|
name = "day-2"
|
||||||
version = "2023.0.0"
|
version = "2023.0.0"
|
||||||
|
|||||||
14
2023/day-16/Cargo.toml
Normal file
14
2023/day-16/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-16"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nom = { workspace = true }
|
||||||
|
itertools = {workspace = true }
|
||||||
|
glam.workspace = true
|
||||||
|
nom_locate.workspace = true
|
||||||
4
2023/day-16/src/lib.rs
Normal file
4
2023/day-16/src/lib.rs
Normal 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
12
2023/day-16/src/main.rs
Normal 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
162
2023/day-16/src/part1.rs
Normal 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
187
2023/day-16/src/part2.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user