192 lines
5.2 KiB
Rust
192 lines
5.2 KiB
Rust
#![feature(test)]
|
|
|
|
use aoc::iter_ext::IterExt;
|
|
use itertools::Itertools;
|
|
use rustc_hash::FxHashSet;
|
|
|
|
#[derive(Debug)]
|
|
struct Input {
|
|
grid: Vec<Vec<Cell>>,
|
|
start: Position,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum Cell {
|
|
Vertical,
|
|
Horizontal,
|
|
NorthEast,
|
|
NorthWest,
|
|
SouthWest,
|
|
SouthEast,
|
|
Ground,
|
|
Start,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
struct Position {
|
|
x: usize,
|
|
y: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum Direction {
|
|
North,
|
|
East,
|
|
South,
|
|
West,
|
|
}
|
|
|
|
impl Direction {
|
|
fn invert(self) -> Self {
|
|
use Direction::{East, North, South, West};
|
|
match self {
|
|
North => South,
|
|
East => West,
|
|
South => North,
|
|
West => East,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Position {
|
|
fn step(self, direction: Direction) -> Option<Self> {
|
|
let x = match direction {
|
|
Direction::West => self.x.checked_sub(1)?,
|
|
Direction::East => self.x + 1,
|
|
Direction::North | Direction::South => self.x,
|
|
};
|
|
let y = match direction {
|
|
Direction::North => self.y.checked_sub(1)?,
|
|
Direction::South => self.y + 1,
|
|
Direction::West | Direction::East => self.y,
|
|
};
|
|
Some(Self { x, y })
|
|
}
|
|
}
|
|
|
|
impl Cell {
|
|
fn connections(self) -> &'static [Direction] {
|
|
use Direction::{East, North, South, West};
|
|
match self {
|
|
Cell::Vertical => &[North, South],
|
|
Cell::Horizontal => &[East, West],
|
|
Cell::NorthEast => &[North, East],
|
|
Cell::NorthWest => &[North, West],
|
|
Cell::SouthWest => &[South, West],
|
|
Cell::SouthEast => &[South, East],
|
|
Cell::Ground => &[],
|
|
Cell::Start => &[North, East, South, West],
|
|
}
|
|
}
|
|
}
|
|
|
|
fn setup(input: &str) -> Input {
|
|
let grid = input
|
|
.lines()
|
|
.map(|line| {
|
|
line.bytes()
|
|
.map(|b| match b {
|
|
b'|' => Cell::Vertical,
|
|
b'-' => Cell::Horizontal,
|
|
b'L' => Cell::NorthEast,
|
|
b'J' => Cell::NorthWest,
|
|
b'7' => Cell::SouthWest,
|
|
b'F' => Cell::SouthEast,
|
|
b'.' => Cell::Ground,
|
|
b'S' => Cell::Start,
|
|
_ => panic!(),
|
|
})
|
|
.collect_vec()
|
|
})
|
|
.collect_vec();
|
|
|
|
let start = grid
|
|
.iter()
|
|
.enumerate()
|
|
.flat_map(|(y, row)| {
|
|
row.iter()
|
|
.enumerate()
|
|
.filter(|&(_, &cell)| cell == Cell::Start)
|
|
.map(move |(x, _)| Position { x, y })
|
|
})
|
|
.next()
|
|
.unwrap();
|
|
|
|
Input { grid, start }
|
|
}
|
|
|
|
fn checked_step(input: &Input, p: Position, d: Direction) -> Option<Position> {
|
|
p.step(d)
|
|
.filter(|q| input.grid[q.y][q.x].connections().contains(&d.invert()))
|
|
}
|
|
|
|
fn find_loop(input: &Input) -> impl Iterator<Item = Position> + '_ {
|
|
IterExt::take_while_inclusive(
|
|
std::iter::successors(
|
|
Some((
|
|
input.start,
|
|
input
|
|
.start
|
|
.step(start_cell(input).connections()[0])
|
|
.unwrap(),
|
|
)),
|
|
|&(p, q)| {
|
|
let r = input.grid[q.y][q.x]
|
|
.connections()
|
|
.iter()
|
|
.filter_map(|&d| checked_step(input, q, d))
|
|
.find(|&r| r != p)
|
|
.unwrap();
|
|
|
|
Some((q, r))
|
|
},
|
|
),
|
|
|&(_, q)| q != input.start,
|
|
)
|
|
.map(|(p, _)| p)
|
|
}
|
|
|
|
fn start_cell(input: &Input) -> Cell {
|
|
let north = checked_step(input, input.start, Direction::North).is_some();
|
|
let east = checked_step(input, input.start, Direction::East).is_some();
|
|
let south = checked_step(input, input.start, Direction::South).is_some();
|
|
let west = checked_step(input, input.start, Direction::West).is_some();
|
|
match (north, east, south, west) {
|
|
(true, false, true, false) => Cell::Vertical,
|
|
(false, true, false, true) => Cell::Horizontal,
|
|
(true, true, false, false) => Cell::NorthEast,
|
|
(true, false, false, true) => Cell::NorthWest,
|
|
(false, false, true, true) => Cell::SouthWest,
|
|
(false, true, true, false) => Cell::SouthEast,
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
fn part1(input: &Input) -> usize {
|
|
find_loop(input).count() / 2
|
|
}
|
|
|
|
fn part2(input: &Input) -> usize {
|
|
let path = find_loop(input).collect::<FxHashSet<_>>();
|
|
input
|
|
.grid
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(y, row)| {
|
|
row.iter()
|
|
.map(|&cell| match cell {
|
|
Cell::Start => start_cell(input),
|
|
_ => cell,
|
|
})
|
|
.enumerate()
|
|
.map(|(x, cell)| (cell, path.contains(&Position { x, y })))
|
|
.fold((false, 0), |(inside, cnt), (cell, on_loop)| match cell {
|
|
Cell::Vertical | Cell::SouthEast | Cell::SouthWest if on_loop => (!inside, cnt),
|
|
_ => (inside, cnt + (inside && !on_loop) as usize),
|
|
})
|
|
.1
|
|
})
|
|
.sum()
|
|
}
|
|
|
|
aoc::main!(2023, 10, ex: 1[a], 2[a], 3[b], 4[b], 5[b]);
|