]> localhost Git - adventofcode2024.git/commitdiff
Complete day 6
authorJack Kinsey <j.jameskinsey@gmail.com>
Sat, 7 Dec 2024 04:07:34 +0000 (23:07 -0500)
committerJack Kinsey <j.jameskinsey@gmail.com>
Sat, 7 Dec 2024 04:07:34 +0000 (23:07 -0500)
Though this solution is correct, it's still apparently quite
inefficient--the debug build takes ~20 seconds to run, and the release
build takes ~1.5 seconds. Considering every other problem combined runs
in less than 0.03 seconds on the release build, this is very bad.

src/day06.rs [new file with mode: 0644]
src/main.rs

diff --git a/src/day06.rs b/src/day06.rs
new file mode 100644 (file)
index 0000000..cdd044d
--- /dev/null
@@ -0,0 +1,243 @@
+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
+        )
+    }
+}
index c57bf00eab0bdf10bbc7c05ba68104b385bfa72d..b4be4178b3f85c459c4c156bba15694369dc0752 100644 (file)
@@ -3,7 +3,7 @@ pub mod day02;
 pub mod day03;
 pub mod day04;
 pub mod day05;
-// pub mod day06;
+pub mod day06;
 // pub mod day07;
 // pub mod day08;
 // pub mod day09;
@@ -26,13 +26,13 @@ pub mod day05;
 
 type Part = fn();
 
-const DAYS: [(Part, Part); 5] = [
+const DAYS: [(Part, Part); 6] = [
     (day01::part1 as fn(), day01::part2 as fn()),
     (day02::part1 as fn(), day02::part2 as fn()),
     (day03::part1 as fn(), day03::part2 as fn()),
     (day04::part1 as fn(), day04::part2 as fn()),
     (day05::part1 as fn(), day05::part2 as fn()),
-    // (day06::part1 as fn(), day06::part2 as fn()),
+    (day06::part1 as fn(), day06::part2 as fn()),
     // (day07::part1 as fn(), day07::part2 as fn()),
     // (day08::part1 as fn(), day08::part2 as fn()),
     // (day09::part1 as fn(), day09::part2 as fn()),