use crate::util::grid::*;
use crate::util::hash::*;
use crate::util::point::*;
use crate::util::thread::*;
use std::sync::atomic::{AtomicUsize, Ordering};
pub fn parse(input: &str) -> Grid<u8> {
Grid::parse(input)
}
pub fn part1(grid: &Grid<u8>) -> usize {
let mut grid = grid.clone();
let mut position = grid.find(b'^').unwrap();
let mut direction = UP;
let mut result = 1;
while grid.contains(position + direction) {
if grid[position + direction] == b'#' {
direction = direction.clockwise();
continue;
}
let next = position + direction;
if grid[next] == b'.' {
result += 1;
grid[next] = b'^';
}
position = next;
}
result
}
pub fn part2(grid: &Grid<u8>) -> usize {
let mut grid = grid.clone();
let mut position = grid.find(b'^').unwrap();
let mut direction = UP;
let mut path = Vec::with_capacity(5_000);
while grid.contains(position + direction) {
if grid[position + direction] == b'#' {
direction = direction.clockwise();
}
let next = position + direction;
if grid[next] == b'.' {
path.push((position, direction));
grid[next] = b'^';
}
position = next;
}
let shortcut = Shortcut::from(&grid);
let total = AtomicUsize::new(0);
spawn_parallel_iterator(&path, |iter| worker(&shortcut, &total, iter));
total.into_inner()
}
fn worker(shortcut: &Shortcut, total: &AtomicUsize, iter: ParIter<'_, (Point, Point)>) {
let mut seen = FastSet::new();
let result = iter
.filter(|(position, direction)| {
seen.clear();
is_cycle(shortcut, &mut seen, *position, *direction)
})
.count();
total.fetch_add(result, Ordering::Relaxed);
}
fn is_cycle(
shortcut: &Shortcut,
seen: &mut FastSet<(Point, Point)>,
mut position: Point,
mut direction: Point,
) -> bool {
let obstacle = position + direction;
while shortcut.up.contains(position) {
if !seen.insert((position, direction)) {
return true;
}
position = match direction {
UP => {
let next = shortcut.up[position];
if position.x == obstacle.x && position.y > obstacle.y && obstacle.y >= next.y {
obstacle - UP
} else {
next
}
}
DOWN => {
let next = shortcut.down[position];
if position.x == obstacle.x && position.y < obstacle.y && obstacle.y <= next.y {
obstacle - DOWN
} else {
next
}
}
LEFT => {
let next = shortcut.left[position];
if position.y == obstacle.y && position.x > obstacle.x && obstacle.x >= next.x {
obstacle - LEFT
} else {
next
}
}
RIGHT => {
let next = shortcut.right[position];
if position.y == obstacle.y && position.x < obstacle.x && obstacle.x <= next.x {
obstacle - RIGHT
} else {
next
}
}
_ => unreachable!(),
};
direction = direction.clockwise();
}
false
}
struct Shortcut {
up: Grid<Point>,
down: Grid<Point>,
left: Grid<Point>,
right: Grid<Point>,
}
impl Shortcut {
fn from(grid: &Grid<u8>) -> Self {
let mut up = grid.same_size_with(ORIGIN);
let mut down = grid.same_size_with(ORIGIN);
let mut left = grid.same_size_with(ORIGIN);
let mut right = grid.same_size_with(ORIGIN);
for x in 0..grid.width {
let mut last = Point::new(x, -1);
for y in 0..grid.height {
let point = Point::new(x, y);
if grid[point] == b'#' {
last = Point::new(x, y + 1);
}
up[point] = last;
}
}
for x in 0..grid.width {
let mut last = Point::new(x, grid.height);
for y in (0..grid.height).rev() {
let point = Point::new(x, y);
if grid[point] == b'#' {
last = Point::new(x, y - 1);
}
down[point] = last;
}
}
for y in 0..grid.height {
let mut last = Point::new(-1, y);
for x in 0..grid.width {
let point = Point::new(x, y);
if grid[point] == b'#' {
last = Point::new(x + 1, y);
}
left[point] = last;
}
}
for y in 0..grid.height {
let mut last = Point::new(grid.width, y);
for x in (0..grid.width).rev() {
let point = Point::new(x, y);
if grid[point] == b'#' {
last = Point::new(x - 1, y);
}
right[point] = last;
}
}
Shortcut { up, down, left, right }
}
}