--- /dev/null
+use std::io::BufRead;
+
+use nalgebra::{DMatrix, Matrix2, Point2, Vector2};
+
+type Map = DMatrix<bool>;
+type Pos = Point2<i32>;
+type Dir = Vector2<i32>;
+
+pub fn read_map<R>(reader: R) -> (Map, Pos)
+where
+ R: BufRead,
+{
+ let mut map = DMatrix::default();
+ let mut guard_position = Pos::default();
+
+ for (i, l) in reader.lines().enumerate() {
+ if map.nrows() < i + 1 {
+ map = map.insert_row(i, false);
+ }
+ for (j, c) in l.unwrap().chars().enumerate() {
+ if map.ncols() < j + 1 {
+ map = map.insert_column(j, false);
+ }
+ if c == '#' {
+ map[(i, j)] = true;
+ } else if c == '^' {
+ guard_position = Pos::new(i as i32, j as i32);
+ }
+ }
+ }
+
+ (map, guard_position)
+}
+
+const ROT_MAT: Matrix2<i32> = Matrix2::new(0, 1, -1, 0);
+
+fn inside(map: &Map, p: Pos) -> bool {
+ p.x >= 0 && p.x < map.nrows() as i32 && p.y >= 0 && p.y < map.ncols() as i32
+}
+
+struct PathIterator<'a> {
+ map: &'a Map,
+ pos: Pos,
+ dir: Dir,
+ obstacle: Option<Pos>,
+}
+
+impl<'a> PathIterator<'a> {
+ pub fn new(map: &'a Map, pos: Pos, dir: Dir, obstacle: Option<Pos>) -> Self {
+ Self {
+ map,
+ pos,
+ dir,
+ obstacle,
+ }
+ }
+}
+
+impl<'a> Iterator for PathIterator<'a> {
+ type Item = (Pos, Dir);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let next_pos = self.pos + self.dir;
+
+ // Going outside the map -> end of sequence.
+ if !inside(self.map, next_pos) {
+ return None;
+ }
+
+ if self.map[(next_pos.x as usize, next_pos.y as usize)]
+ || (self.obstacle.is_some() && self.obstacle.unwrap() == next_pos)
+ {
+ self.dir = ROT_MAT * self.dir;
+ } else {
+ self.pos = next_pos;
+ }
+
+ Some((self.pos, self.dir))
+ }
+}
+
+pub fn nb_position_visited_by_guard(map: &Map, guard_pos: Pos) -> usize {
+ let mut nb_visited = 1;
+ let mut map_visited = Map::repeat(map.nrows(), map.ncols(), false);
+
+ map_visited[(guard_pos.x as usize, guard_pos.y as usize)] = true;
+
+ for (pos, _) in PathIterator::new(map, guard_pos, Dir::new(-1, 0), None) {
+ if !map_visited[(pos.x as usize, pos.y as usize)] {
+ map_visited[(pos.x as usize, pos.y as usize)] = true;
+ nb_visited += 1;
+ }
+ }
+
+ nb_visited
+}
+
+pub fn nb_possible_obstruction_position(map: &Map, initial_pos: Pos) -> usize {
+ let initial_dir = Dir::new(-1, 0);
+ let mut map_visited = DMatrix::<Option<Dir>>::repeat(map.nrows(), map.ncols(), None);
+
+ let mut nb_loops = 0;
+ for (pos, dir) in vec![(initial_pos, initial_dir)]
+ .into_iter()
+ .chain(PathIterator::new(map, initial_pos, initial_dir, None))
+ {
+ if map_visited[(pos.x as usize, pos.y as usize)].is_none() {
+ map_visited[(pos.x as usize, pos.y as usize)] = Some(dir);
+ }
+ let obstacle_pos = pos + dir;
+ if inside(map, obstacle_pos)
+ && !map[(obstacle_pos.x as usize, obstacle_pos.y as usize)]
+ && map_visited[(obstacle_pos.x as usize, obstacle_pos.y as usize)].is_none()
+ {
+ if does_it_loop(
+ PathIterator::new(map, pos, dir, Some(obstacle_pos)),
+ &map_visited,
+ ) {
+ nb_loops += 1;
+ }
+ }
+ }
+ nb_loops
+}
+
+fn does_it_loop(path: PathIterator, map_visited: &DMatrix<Option<Dir>>) -> bool {
+ let mut map_visited = map_visited.clone();
+ for (pos, dir) in path {
+ let visited = map_visited[(pos.x as usize, pos.y as usize)];
+ if visited.is_none() {
+ map_visited[(pos.x as usize, pos.y as usize)] = Some(dir);
+ } else if visited.unwrap() == dir {
+ return true;
+ }
+ }
+ return false;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static MAP: &str = "....#.....
+.........#
+..........
+..#.......
+.......#..
+..........
+.#..^.....
+........#.
+#.........
+......#...";
+
+ #[test]
+ fn test_part1() {
+ let (map, guard_pos) = read_map(MAP.as_bytes());
+ let n = nb_position_visited_by_guard(&map, guard_pos);
+ println!("n: {}", n);
+ assert_eq!(n, 41);
+ }
+
+ #[test]
+ fn test_part2() {
+ let (map, guard_pos) = read_map(MAP.as_bytes());
+ let n = nb_possible_obstruction_position(&map, guard_pos);
+ println!("n: {}", n);
+ assert_eq!(n, 6);
+ }
+
+ static MAP2: &str = ".#..
+.^.#
+....
+..#.";
+
+ static MAP3: &str = "....
+.^.#
+#...
+..#.";
+
+ static MAP4: &str = ".#..
+...#
+#...
+.^..";
+
+ static MAP5: &str = ".#..
+...#
+#...
+.^..";
+
+ static MAP6: &str = "...
+#.#
+#^#
+.#.";
+
+ #[test]
+ fn test_part2_tiny_maps() {
+ let (map, guard_pos) = read_map(MAP2.as_bytes());
+ assert_eq!(nb_possible_obstruction_position(&map, guard_pos), 1);
+
+ let (map, guard_pos) = read_map(MAP3.as_bytes());
+ assert_eq!(nb_possible_obstruction_position(&map, guard_pos), 1);
+
+ let (map, guard_pos) = read_map(MAP4.as_bytes());
+ assert_eq!(nb_possible_obstruction_position(&map, guard_pos), 1);
+
+ let (map, guard_pos) = read_map(MAP5.as_bytes());
+ assert_eq!(nb_possible_obstruction_position(&map, guard_pos), 1);
+
+ let (map, guard_pos) = read_map(MAP6.as_bytes());
+ assert_eq!(nb_possible_obstruction_position(&map, guard_pos), 2);
+ }
+}