--- /dev/null
+use std::collections::{HashMap, HashSet};
+
+fn input() -> &'static str {
+ include_str!("../input/day06.txt")
+}
+
+#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
+enum Dir {
+ N,
+ E,
+ S,
+ W,
+}
+
+#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
+enum Tile {
+ Space,
+ Obstacle,
+ Guard(Dir),
+}
+
+fn parse(input: &str) -> Vec<Vec<Tile>> {
+ input
+ .split_terminator('\n')
+ .map(|v| {
+ v.split("")
+ .filter(|s| !s.is_empty())
+ .map(|c| match c {
+ "." => Tile::Space,
+ "#" => Tile::Obstacle,
+ "^" => Tile::Guard(Dir::N),
+ ">" => Tile::Guard(Dir::E),
+ "v" => Tile::Guard(Dir::S),
+ "<" => Tile::Guard(Dir::W),
+ _ => panic!(),
+ })
+ .collect()
+ })
+ .collect()
+}
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+struct GuardWalk<'a> {
+ map: &'a Vec<Vec<Tile>>,
+ guard: Option<Tile>,
+ pos: Option<(usize, usize)>,
+ block: Option<(usize, usize)>,
+}
+
+impl<'a> GuardWalk<'a> {
+ fn new(map: &'a Vec<Vec<Tile>>) -> Self {
+ GuardWalk {
+ map,
+ guard: None,
+ pos: None,
+ block: None,
+ }
+ }
+
+ fn new_blocked(map: &'a Vec<Vec<Tile>>, block: (usize, usize)) -> Self {
+ GuardWalk {
+ map,
+ guard: None,
+ pos: None,
+ block: Some(block),
+ }
+ }
+
+ fn has_cycle(mut self) -> bool {
+ let mut counts: HashMap<((usize, usize), Tile), u32> = HashMap::new();
+ while let Some(pos) = self.next() {
+ if let Some(guard) = self.guard {
+ let count = counts.entry((pos, guard)).or_default();
+ *count += 1;
+ if *count >= 3 {
+ return true;
+ }
+ }
+ }
+ false
+ }
+}
+
+impl<'a> Iterator for GuardWalk<'a> {
+ type Item = (usize, usize);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.guard.is_none() || self.pos.is_none() {
+ self.pos = self
+ .map
+ .iter()
+ .zip(0..)
+ .flat_map(move |(v, i)| {
+ v.iter()
+ .zip(0..)
+ .filter(move |(&c, _)| matches!(c, Tile::Guard(_)))
+ .map(move |(_, j)| (i, j))
+ })
+ .last();
+ if let Some(pos) = self.pos {
+ self.guard = Some(self.map[pos.0][pos.1]);
+ if matches!(self.block, Some(block) if block == pos) {
+ return None;
+ }
+ }
+ self.pos
+ } else {
+ let n = self.map.len();
+ if let Some((i0, j0)) = self.pos {
+ let (i1, j1) = match self.guard {
+ Some(Tile::Guard(Dir::N)) => (i0.checked_sub(1), Some(j0)),
+ Some(Tile::Guard(Dir::E)) => (Some(i0), Some(j0 + 1)),
+ Some(Tile::Guard(Dir::S)) => (Some(i0 + 1), Some(j0)),
+ Some(Tile::Guard(Dir::W)) => (Some(i0), j0.checked_sub(1)),
+ _ => (None, None),
+ };
+ if let (Some(i), Some(j)) = (i1, j1) {
+ if i < n && j < n {
+ if self.map[i][j] == Tile::Obstacle
+ || matches!(self.block, Some(block) if (i, j) == block)
+ {
+ self.guard = match self.guard {
+ Some(Tile::Guard(Dir::N)) => Some(Tile::Guard(Dir::E)),
+ Some(Tile::Guard(Dir::E)) => Some(Tile::Guard(Dir::S)),
+ Some(Tile::Guard(Dir::S)) => Some(Tile::Guard(Dir::W)),
+ Some(Tile::Guard(Dir::W)) => Some(Tile::Guard(Dir::N)),
+ _ => None,
+ }
+ } else {
+ self.pos = Some((i, j));
+ }
+ self.pos
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ }
+}
+
+pub fn part1() {
+ let n = GuardWalk::new(&parse(input()))
+ .collect::<HashSet<_>>()
+ .len();
+ println!("Day 6 Part 1: {}", n);
+}
+
+pub fn part2() {
+ let map = parse(input());
+ let n = GuardWalk::new(&map)
+ .collect::<HashSet<_>>()
+ .iter()
+ .filter(|&&p| GuardWalk::new_blocked(&map, p).has_cycle())
+ .count();
+ println!("Day 6 Part 2: {}", n);
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const INPUT_STR: &str = concat!(
+ "....#.....\n",
+ ".........#\n",
+ "..........\n",
+ "..#.......\n",
+ ".......#..\n",
+ "..........\n",
+ ".#..^.....\n",
+ "........#.\n",
+ "#.........\n",
+ "......#...\n",
+ );
+
+ #[rustfmt::skip]
+ #[test]
+ fn test_parse() {
+ use Tile::*;
+ use Dir::*;
+ assert_eq!(
+ parse(INPUT_STR),
+ [
+ [Space, Space, Space, Space, Obstacle, Space, Space, Space, Space, Space],
+ [Space, Space, Space, Space, Space, Space, Space, Space, Space, Obstacle],
+ [Space, Space, Space, Space, Space, Space, Space, Space, Space, Space],
+ [Space, Space, Obstacle, Space, Space, Space, Space, Space, Space, Space],
+ [Space, Space, Space, Space, Space, Space, Space, Obstacle, Space, Space],
+ [Space, Space, Space, Space, Space, Space, Space, Space, Space, Space],
+ [Space, Obstacle, Space, Space, Guard(N), Space, Space, Space, Space, Space],
+ [Space, Space, Space, Space, Space, Space, Space, Space, Obstacle, Space],
+ [Obstacle, Space, Space, Space, Space, Space, Space, Space, Space, Space],
+ [Space, Space, Space, Space, Space, Space, Obstacle, Space, Space, Space],
+ ]
+ )
+ }
+
+ #[test]
+ fn test_guard_walk() {
+ assert_eq!(
+ GuardWalk::new(&parse(INPUT_STR))
+ .take(8)
+ .collect::<Vec<_>>(),
+ [
+ (6, 4),
+ (5, 4),
+ (4, 4),
+ (3, 4),
+ (2, 4),
+ (1, 4),
+ (1, 4),
+ (1, 5)
+ ]
+ )
+ }
+
+ #[test]
+ fn test_walk_length() {
+ assert_eq!(
+ GuardWalk::new(&parse(INPUT_STR))
+ .collect::<HashSet<_>>()
+ .len(),
+ 41
+ )
+ }
+
+ #[test]
+ fn test_blocked_cycles() {
+ let map = parse(INPUT_STR);
+ assert_eq!(
+ GuardWalk::new(&map)
+ .collect::<HashSet<_>>()
+ .iter()
+ .filter(|&&p| GuardWalk::new_blocked(&map, p).has_cycle())
+ .count(),
+ 6
+ )
+ }
+}