use crate::day_6::Direction::*;
use itertools::Itertools;
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs;
use std::iter::successors;
pub fn run() {
let contents = fs::read_to_string("res/day-6-input.txt").expect("Failed to read file");
let (lab, guard) = parse_input(&contents);
println!(
"The guard visits {} positions",
count_guard_positions(&guard, &lab)
);
println!(
"There are {} positions where obstructions will cause a loop",
count_obstructions_causing_loops(&guard, &lab)
)
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash)]
enum Direction {
UP,
RIGHT,
DOWN,
LEFT,
}
impl Direction {
fn turn(&self) -> Direction {
match self {
UP => RIGHT,
RIGHT => DOWN,
DOWN => LEFT,
LEFT => UP,
}
}
}
type Position = (usize, usize);
#[derive(Eq, PartialEq, Debug, Clone)]
struct Lab {
width: usize,
height: usize,
obstructions: HashSet<Position>,
}
impl Lab {
fn with_obstruction(&self, position: Position) -> Lab {
let mut obstructions = self.obstructions.clone();
obstructions.insert(position);
Lab {
obstructions,
..self.clone()
}
}
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash)]
struct Guard {
position: Position,
direction: Direction,
}
impl Guard {
fn new(position: Position, direction: Direction) -> Guard {
Guard {
position,
direction,
}
}
fn step_row(&self, delta: isize, &Lab { height, .. }: &Lab) -> Option<Position> {
let (row, column) = self.position;
let new_row = row.checked_add_signed(delta).filter(|&c| c < height);
new_row.zip(Some(column.clone()))
}
fn step_column(&self, delta: isize, &Lab { width, .. }: &Lab) -> Option<Position> {
let (row, column) = self.position;
let new_column = column.checked_add_signed(delta).filter(|&c| c < width);
Some(row.clone()).zip(new_column)
}
fn next_position(&self, lab: &Lab) -> Option<(usize, usize)> {
match self.direction {
UP => self.step_row(-1, lab),
RIGHT => self.step_column(1, lab),
DOWN => self.step_row(1, lab),
LEFT => self.step_column(-1, lab),
}
}
fn with_position(&self, position: Position) -> Guard {
Guard {
position,
direction: self.direction,
}
}
fn with_direction(&self, direction: Direction) -> Guard {
Guard { direction, ..*self }
}
fn take_step(&self, lab: &Lab) -> Option<Guard> {
match self.next_position(lab) {
Some(position) if lab.obstructions.contains(&position) => {
Some(self.with_direction(self.direction.turn()))
}
Some(position) => Some(self.with_position(position)),
None => None,
}
}
}
fn parse_input(input: &String) -> (Lab, Guard) {
let mut lines = input.lines();
let width = lines.next().unwrap().len();
let height = lines.count() + 1;
let mut guard = None;
let mut obstructions = HashSet::new();
for (row, line) in input.lines().enumerate() {
for (column, char) in line.chars().enumerate() {
match char {
'#' => {
obstructions.insert((row, column));
}
'^' => {
guard = Some(Guard::new((row, column), UP));
}
_ => (),
}
}
}
(
Lab {
width,
height,
obstructions,
},
guard.unwrap(),
)
}
fn route_iter<'a>(guard: &'a Guard, lab: &'a Lab) -> impl Iterator<Item = Guard> + 'a {
successors(Some(guard.clone()), |g| g.take_step(lab))
}
fn count_guard_positions(guard: &Guard, lab: &Lab) -> usize {
route_iter(guard, &lab).map(|g| g.position).unique().count()
}
fn is_loop(guard: &Guard, lab: &Lab) -> bool {
route_iter(&guard, &lab).duplicates().next().is_some()
}
fn count_obstructions_causing_loops(guard: &Guard, lab: &Lab) -> usize {
route_iter(guard, lab)
.flat_map(|g| Some(g).zip(g.next_position(lab)))
.filter(|(_, pos)| *pos != guard.position)
.unique_by(|(_, pos)| *pos)
.par_bridge()
.filter(|(g, pos)| is_loop(g, &lab.with_obstruction(*pos)))
.count()
}
#[cfg(test)]
mod tests {
use crate::day_6::*;
fn example_lab() -> Lab {
Lab {
width: 10,
height: 10,
obstructions: vec![
(0, 4),
(1, 9),
(3, 2),
(4, 7),
(6, 1),
(7, 8),
(8, 0),
(9, 6),
]
.into_iter()
.collect(),
}
}
#[test]
fn can_parse_input() {
let input = "....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#..."
.to_string();
let (lab, guard) = parse_input(&input);
assert_eq!(lab, example_lab());
assert_eq!(guard, Guard::new((6, 4), UP));
}
#[test]
fn can_take_step() {
let lab = example_lab();
let examples = vec![
(Guard::new((6, 4), UP), Some(Guard::new((5, 4), UP))),
(Guard::new((1, 4), UP), Some(Guard::new((1, 4), RIGHT))),
(Guard::new((1, 4), RIGHT), Some(Guard::new((1, 5), RIGHT))),
(Guard::new((1, 8), RIGHT), Some(Guard::new((1, 8), DOWN))),
(Guard::new((1, 8), DOWN), Some(Guard::new((2, 8), DOWN))),
(Guard::new((6, 8), DOWN), Some(Guard::new((6, 8), LEFT))),
(Guard::new((6, 8), LEFT), Some(Guard::new((6, 7), LEFT))),
(Guard::new((6, 2), LEFT), Some(Guard::new((6, 2), UP))),
(Guard::new((0, 0), UP), None),
(Guard::new((9, 9), RIGHT), None),
(Guard::new((9, 9), DOWN), None),
(Guard::new((0, 0), LEFT), None),
];
for (guard, expected) in examples {
assert_eq!(guard.take_step(&lab), expected)
}
}
#[test]
fn can_count_guard_positions() {
assert_eq!(
count_guard_positions(&Guard::new((6, 4), UP), &example_lab()),
41
);
}
#[test]
fn can_check_if_route_loops() {
let lab = example_lab();
let guard = Guard::new((6, 4), UP);
assert_eq!(is_loop(&guard, &lab), false);
let looping_positions = vec![(6, 3), (7, 6), (7, 7), (8, 1), (8, 3), (9, 7)];
for position in looping_positions {
assert!(
is_loop(&guard, &example_lab().with_obstruction(position)),
"Should loop with an obstruction at {position:?}"
)
}
}
#[test]
fn can_count_obstructions() {
assert_eq!(
count_obstructions_causing_loops(&Guard::new((6, 4), UP), &example_lab()),
6
)
}
}