diff --git a/README.md b/README.md index 8e47ae8..97acee7 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,16 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. ## Benchmarks -| Day | Part 1 | Part 2 | -| :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `61.4µs` | `106.8µs` | -| [Day 2](./src/bin/02.rs) | `220.6µs` | `404.8µs` | -| [Day 3](./src/bin/03.rs) | `561.0µs` | `565.5µs` | -| [Day 4](./src/bin/04.rs) | `560.8µs` | `161.9µs` | -| [Day 5](./src/bin/05.rs) | `340.4µs` | `328.0µs` | - -**Total: 3.31ms** +| Day | Part 1 | Part 2 | +|:---------------------------------------:| :---: | :---: | +| [Day 1](./src/bin/01.rs) | `61.4µs` | `106.8µs` | +| [Day 2](./src/bin/02.rs) | `220.6µs` | `404.8µs` | +| [Day 3](./src/bin/03.rs) | `561.0µs` | `565.5µs` | +| [Day 4](./src/bin/04.rs) | `560.8µs` | `161.9µs` | +| [Day 5](./src/bin/05.rs) | `344.6µs` | `323.6µs` | +| [Day 6](./src/bin/06.rs) (unoptimized) | `494.4µs` | `956.4ms` | + +**Total: 960.21ms** --- diff --git a/data/examples/06.txt b/data/examples/06.txt new file mode 100644 index 0000000..b60e466 --- /dev/null +++ b/data/examples/06.txt @@ -0,0 +1,10 @@ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... \ No newline at end of file diff --git a/src/bin/06.rs b/src/bin/06.rs new file mode 100644 index 0000000..c4be273 --- /dev/null +++ b/src/bin/06.rs @@ -0,0 +1,141 @@ +use std::collections::HashSet; +use itertools::Itertools; + +advent_of_code::solution!(6); + +struct Map { + dimensions: (isize, isize), + obstacles: HashSet<(isize, isize)>, + visited_pos: HashSet<(isize, isize)>, + starting_pos: (isize, isize), +} + +impl Map { + + fn parse(input: &str) -> Map { + let (mut columns, mut rows) = (0, 0); + let mut starting_pos = (0, 0); + let mut obstacles = HashSet::new(); + let visited_pos = HashSet::new(); + input.lines() + .enumerate() + .map(|(y, line)| { + rows = y; + line.chars() + .enumerate() + .map(|(x, char)| { + columns = x; + match char { + '^' => starting_pos = (x as isize, y as isize), + '#' => { + obstacles.insert((x as isize, y as isize)); + } + _ => {} + } + char + }) + .collect_vec() + }) + .collect_vec(); + Map { + dimensions: (columns as isize + 1, rows as isize + 1), + obstacles, + visited_pos, + starting_pos, + } + } + + fn is_obstacle(&self, pos: &(isize, isize)) -> bool { + self.obstacles.contains(pos) + } + + fn farthest_point(&mut self, starting_pos: &(isize, isize), direction: &(isize, isize)) -> Option<(isize, isize)> { + let mut pos = *starting_pos; + while self.is_in_map(&pos) { + self.visited_pos.insert(pos); + let next_pos = (pos.0 + direction.0, pos.1 + direction.1); + if self.is_obstacle(&next_pos) { + return Some(pos); + } + pos = next_pos; + } + None + } + + fn is_in_map(&self, pos: &(isize, isize)) -> bool { + (0..self.dimensions.0).contains(&pos.0) && (0..self.dimensions.1).contains(&pos.1) + } + + fn predict_path(&mut self) -> bool { + let mut current_pos = self.starting_pos; + let mut direction = 0; + while let Some(next_pos) = self.farthest_point(¤t_pos, &DIRECTIONS[direction]) { + direction = (direction + 1) % 4; + current_pos = next_pos; + } + false + } + + fn has_loop(&mut self) -> bool { + let mut current_pos = self.starting_pos; + let mut direction = 0; + let mut turns = HashSet::new(); + while let Some(next_pos) = self.farthest_point(¤t_pos, &DIRECTIONS[direction]) { + if turns.contains(&(current_pos, direction)) { + return true; + } + turns.insert((current_pos, direction)); + direction = (direction + 1) % 4; + current_pos = next_pos; + } + false + } + +} + +const DIRECTIONS: [(isize, isize); 4] = [ + (0, -1), + (1, 0), + (0, 1), + (-1, 0), +]; + +pub fn part_one(input: &str) -> Option { + let mut map = Map::parse(input); + map.predict_path(); + Some(map.visited_pos.len() as u32) +} + +pub fn part_two(input: &str) -> Option { + let mut obstructions = 0; + let mut map = Map::parse(input); + map.predict_path(); + let mut original_path = map.visited_pos; + original_path.remove(&map.starting_pos); + for pos in original_path { + map.obstacles.insert(pos); + map.visited_pos = HashSet::new(); + if map.has_loop() { + obstructions += 1; + } + map.obstacles.remove(&pos); + } + Some(obstructions) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(41)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(6)); + } +}