376 lines
11 KiB
Rust
376 lines
11 KiB
Rust
#![warn(clippy::all, clippy::pedantic)]
|
|
|
|
use std::{collections::HashMap, fmt::Display, iter::successors};
|
|
|
|
use glam::IVec2;
|
|
use itertools::Itertools;
|
|
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, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
|
|
enum PipeFrom {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
impl PipeFrom {
|
|
fn from_ivecs(a: IVec2, b: IVec2) -> Option<Self> {
|
|
match (a - b).into() {
|
|
(0, 1) => Some(Self::Down),
|
|
(0, -1) => Some(Self::Up),
|
|
(1, 0) => Some(Self::Right),
|
|
(-1, 0) => Some(Self::Left),
|
|
_ => None,
|
|
//value => unimplemented!("this can't be {a:?} - {b:?} = {value:?}"),
|
|
}
|
|
}
|
|
fn to_ivec(self) -> IVec2 {
|
|
match self {
|
|
PipeFrom::Up => (0, -1).into(),
|
|
PipeFrom::Down => (0, 1).into(),
|
|
PipeFrom::Left => (-1, 0).into(),
|
|
PipeFrom::Right => (1, 0).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
|
|
enum PipeType {
|
|
// 'S'
|
|
Start,
|
|
// '-'
|
|
Horizontal,
|
|
// '|'
|
|
Vertical,
|
|
// 'F'
|
|
DownRight,
|
|
// '7'
|
|
DownLeft,
|
|
// 'L'
|
|
UpRight,
|
|
// 'J'
|
|
UpLeft,
|
|
// '.' -this is so we can parse but should be discarded
|
|
None,
|
|
Outer,
|
|
Inner,
|
|
}
|
|
|
|
impl PipeType {
|
|
fn get_adjacents(self) -> Vec<IVec2> {
|
|
match self {
|
|
PipeType::Start => vec![(-1, 0).into(), (0, -1).into(), (0, 1).into(), (1, 0).into()],
|
|
PipeType::Horizontal => vec![(1, 0).into(), (-1, 0).into()],
|
|
PipeType::Vertical => vec![(0, 1).into(), (0, -1).into()],
|
|
PipeType::DownRight => vec![(0, 1).into(), (1, 0).into()],
|
|
PipeType::DownLeft => vec![(0, 1).into(), (-1, 0).into()],
|
|
PipeType::UpRight => vec![(0, -1).into(), (1, 0).into()],
|
|
PipeType::UpLeft => vec![(0, -1).into(), (-1, 0).into()],
|
|
value => unimplemented!("this should never have been called for type {value:?}"),
|
|
}
|
|
}
|
|
}
|
|
impl Display for PipeType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self {
|
|
Self::Start => "S",
|
|
Self::Horizontal => "-",
|
|
Self::Vertical => "|",
|
|
Self::DownRight => "F",
|
|
Self::DownLeft => "7",
|
|
Self::UpRight => "L",
|
|
Self::UpLeft => "J",
|
|
Self::None => ".",
|
|
Self::Outer => "O",
|
|
Self::Inner => "I",
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct Pipe {
|
|
pub pipe_type: PipeType,
|
|
pub position: IVec2,
|
|
}
|
|
|
|
impl Pipe {
|
|
fn get_adjacent(&self) -> Vec<(IVec2, PipeFrom)> {
|
|
self.pipe_type
|
|
.get_adjacents()
|
|
.into_iter()
|
|
.map(|x| x + self.position)
|
|
.filter_map(|x| PipeFrom::from_ivecs(self.position, x).map(|y| (x, y)))
|
|
.collect()
|
|
}
|
|
fn next(&self, from: PipeFrom) -> IVec2 {
|
|
use PipeFrom::{Down, Left, Right, Up};
|
|
use PipeType::{DownLeft, DownRight, Horizontal, UpLeft, UpRight, Vertical};
|
|
match (from, self.pipe_type) {
|
|
(Up, Vertical) | (Left, DownLeft) | (Right, DownRight) => Down,
|
|
(Up, UpLeft) | (Down, DownLeft) | (Right, Horizontal) => Left,
|
|
(Up, UpRight) | (Down, DownRight) | (Left, Horizontal) => Right,
|
|
(Down, Vertical) | (Left, UpLeft) | (Right, UpRight) => Up,
|
|
_ => unimplemented!("no"),
|
|
}
|
|
.to_ivec()
|
|
+ self.position
|
|
}
|
|
}
|
|
|
|
/// day 10 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
|
|
/// usize
|
|
#[must_use]
|
|
pub fn part2(input: &str) -> String {
|
|
let input = Span::new(input);
|
|
let (_, grid) = parse_input(input).expect("aoc always parse");
|
|
let start_node = grid
|
|
.values()
|
|
.find(|x| x.pipe_type == PipeType::Start)
|
|
.expect("has a start");
|
|
let start_node_true_type = match &start_node
|
|
.get_adjacent()
|
|
.iter()
|
|
.filter_map(|(x, from)| grid.get(x).map(|y| (y, *from)))
|
|
.filter_map(|(x, from)| {
|
|
x.get_adjacent()
|
|
.iter()
|
|
.map(|(y, _)| y)
|
|
.contains(&start_node.position)
|
|
.then_some(from)
|
|
})
|
|
.collect::<Vec<_>>()[..]
|
|
{
|
|
[PipeFrom::Up, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Up] => PipeType::DownRight,
|
|
[PipeFrom::Up, PipeFrom::Right] | [PipeFrom::Right, PipeFrom::Up] => PipeType::DownLeft,
|
|
[PipeFrom::Down, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Down] => PipeType::UpRight,
|
|
[PipeFrom::Down, PipeFrom::Right] | [PipeFrom::Right, PipeFrom::Down] => PipeType::UpLeft,
|
|
[PipeFrom::Up, PipeFrom::Down] | [PipeFrom::Down, PipeFrom::Up] => PipeType::Vertical,
|
|
[PipeFrom::Right, PipeFrom::Left] | [PipeFrom::Left, PipeFrom::Right] => {
|
|
PipeType::Horizontal
|
|
}
|
|
_ => PipeType::Start,
|
|
};
|
|
|
|
let mut pieces = HashMap::new();
|
|
pieces.insert(start_node.position, start_node_true_type);
|
|
|
|
successors(
|
|
Some(
|
|
start_node
|
|
.get_adjacent()
|
|
.iter()
|
|
.filter_map(|(x, from)| grid.get(x).map(|y| (y, *from)))
|
|
.filter(|(x, _)| {
|
|
x.get_adjacent()
|
|
.iter()
|
|
.map(|(y, _)| y)
|
|
.contains(&start_node.position)
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
),
|
|
|front_nodes| {
|
|
if front_nodes[0].0 == front_nodes[1].0 {
|
|
return None;
|
|
}
|
|
Some(
|
|
front_nodes
|
|
.iter()
|
|
.filter_map(|(pipe, from)| {
|
|
grid.get(&pipe.next(*from))
|
|
.map(|x| (x, PipeFrom::from_ivecs(pipe.position, x.position).unwrap()))
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
},
|
|
)
|
|
.filter(|x| !x.is_empty())
|
|
.for_each(|x| {
|
|
for (pipe, _) in &x {
|
|
pieces.insert(pipe.position, pipe.pipe_type);
|
|
}
|
|
});
|
|
let corners = pieces.keys().fold(
|
|
((i32::MAX, i32::MAX), (i32::MIN, i32::MIN)),
|
|
|((minimum_x, min_y), (maximal_x, max_y)), pos| {
|
|
let minimum_x = minimum_x.min(pos.x);
|
|
let miny = min_y.min(pos.y);
|
|
let maximal_x = maximal_x.max(pos.x);
|
|
let maxy = max_y.max(pos.y);
|
|
((minimum_x, miny), (maximal_x, maxy))
|
|
},
|
|
);
|
|
/* Debug
|
|
(corners.0 .1..=corners.1 .1).for_each(|y| {
|
|
(corners.0.0..=corners.1.0).for_each(|x| {
|
|
let p = pieces.get(&(x,y).into()).unwrap_or(&PipeType::None);
|
|
print!("{p}");
|
|
});
|
|
print!("\n");
|
|
});
|
|
*/
|
|
(corners.0 .1..=corners.1 .1).for_each(|y| {
|
|
let mut status = false;
|
|
(corners.0 .0..=corners.1 .0)
|
|
.map(|x| IVec2::new(x, y))
|
|
.for_each(|pos| {
|
|
if let Some(piece) = pieces.get(&pos) {
|
|
status = match piece {
|
|
PipeType::Vertical | PipeType::DownRight | PipeType::DownLeft => !status,
|
|
_ => status,
|
|
};
|
|
} else if status {
|
|
pieces.insert(pos, PipeType::Inner);
|
|
} else {
|
|
pieces.insert(pos, PipeType::Outer);
|
|
}
|
|
});
|
|
});
|
|
/* Debug
|
|
println!();
|
|
(corners.0 .1..=corners.1 .1).for_each(|y| {
|
|
(corners.0.0..=corners.1.0).for_each(|x| {
|
|
let p = pieces.get(&(x,y).into()).unwrap();
|
|
print!("{p}");
|
|
});
|
|
print!("\n");
|
|
});
|
|
*/
|
|
pieces
|
|
.values()
|
|
.filter(|x| **x == PipeType::Inner)
|
|
.count()
|
|
.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_pipe(input: Span) -> IResult<Span, Pipe> {
|
|
alt((
|
|
tag("S").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::Start,
|
|
position: position.extra,
|
|
}),
|
|
tag("-").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::Horizontal,
|
|
position: position.extra,
|
|
}),
|
|
tag("|").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::Vertical,
|
|
position: position.extra,
|
|
}),
|
|
tag("F").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::DownRight,
|
|
position: position.extra,
|
|
}),
|
|
tag("7").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::DownLeft,
|
|
position: position.extra,
|
|
}),
|
|
tag("L").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::UpRight,
|
|
position: position.extra,
|
|
}),
|
|
tag("J").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::UpLeft,
|
|
position: position.extra,
|
|
}),
|
|
tag(".").map(with_xy).map(|position| Pipe {
|
|
pipe_type: PipeType::None,
|
|
position: position.extra,
|
|
}),
|
|
))(input)
|
|
}
|
|
|
|
fn parse_input(input: Span) -> IResult<Span, HashMap<IVec2, Pipe>> {
|
|
fold_many1(
|
|
terminated(many1(parse_pipe), alt((complete::line_ending, eof))),
|
|
HashMap::new,
|
|
|mut acc, x| {
|
|
x.into_iter()
|
|
.filter(|x| x.pipe_type != PipeType::None)
|
|
.for_each(|x| {
|
|
acc.insert(x.position, x);
|
|
});
|
|
acc
|
|
},
|
|
)(input)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
use rstest::rstest;
|
|
|
|
#[rstest]
|
|
#[case(
|
|
"...........
|
|
.S-------7.
|
|
.|F-----7|.
|
|
.||.....||.
|
|
.||.....||.
|
|
.|L-7.F-J|.
|
|
.|..|.|..|.
|
|
.L--J.L--J.
|
|
...........",
|
|
"4"
|
|
)]
|
|
#[case(
|
|
".F----7F7F7F7F-7....
|
|
.|F--7||||||||FJ....
|
|
.||.FJ||||||||L7....
|
|
FJL7L7LJLJ||LJ.L-7..
|
|
L--J.L7...LJS7F-7L7.
|
|
....F-J..F7FJ|L7L7L7
|
|
....L7.F7||L7|.L7L7|
|
|
.....|FJLJ|FJ|F7|.LJ
|
|
....FJL-7.||.||||...
|
|
....L---J.LJ.LJLJ...",
|
|
"8"
|
|
)]
|
|
#[case(
|
|
"FF7FSF7F7F7F7F7F---7
|
|
L|LJ||||||||||||F--J
|
|
FL-7LJLJ||||||LJL-77
|
|
F--JF--7||LJLJ7F7FJ-
|
|
L---JF-JLJ.||-FJLJJ7
|
|
|F|F-JF---7F7-L7L|7|
|
|
|FFJF7L7F-JF7|JL---7
|
|
7-L-JL7||F7|L7F-7F7|
|
|
L.L7LFJ|||||FJL7||LJ
|
|
L7JLJL-JLJLJL--JLJ.L",
|
|
"10"
|
|
)]
|
|
|
|
fn part2_works(#[case] input: &str, #[case] expected: &str) {
|
|
let result = part2(input);
|
|
assert_eq!(result, expected);
|
|
}
|
|
}
|