diff --git a/README.md b/README.md index 9889722..89a5c70 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,10 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 8](./src/bin/08.rs) | `10.2µs` | `12.4µs` | | [Day 9](./src/bin/09.rs) | `2.9ms` | `49.1ms` | | [Day 10](./src/bin/10.rs) | `96.9µs` | `97.3µs` | -| [Day 11](./src/bin/11.rs) | `216.1µs` | `10.9ms` | +| [Day 11](./src/bin/11.rs) | `218.3µs` | `11.2ms` | +| [Day 12](./src/bin/12.rs) | `3.0ms` | `2.8ms` | -**Total: 187.23ms** +**Total: 193.33ms** --- diff --git a/data/examples/12.txt b/data/examples/12.txt new file mode 100644 index 0000000..0b328f1 --- /dev/null +++ b/data/examples/12.txt @@ -0,0 +1,10 @@ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE \ No newline at end of file diff --git a/src/bin/12.rs b/src/bin/12.rs new file mode 100644 index 0000000..ea6a325 --- /dev/null +++ b/src/bin/12.rs @@ -0,0 +1,165 @@ +use std::collections::HashSet; +use itertools::Itertools; + +advent_of_code::solution!(12); + +const DIRECTIONS: [(isize, isize); 4] = [ + (0, -1), + (1, 0), + (0, 1), + (-1, 0), +]; + +#[derive(Debug)] +struct Grid { + width: usize, + height: usize, + data: Vec, +} + +impl Grid { + fn parse(input: &str) -> Grid { + let width = input.lines().next().unwrap_or_default().len(); + let data = input.lines() + .flat_map(|line| line.chars()) + .collect_vec(); + let height = data.len() / width; + Grid { + height, + width, + data, + } + } + + fn get_perimeter(&self, index: usize, target: &char, visited: &mut HashSet) -> u32 { + if visited.contains(&index) { + return 0; + } + if self.data[index] != *target { + return 1; + } + visited.insert(index); + DIRECTIONS.iter() + .map(|direction| self.offset(&index, direction)) + .map(|pos| pos.map(|pos| self.get_perimeter(pos, target, visited)).unwrap_or(1)) + .sum() + } + + fn get_vertices(&self, index: usize, target: &char, visited: &mut HashSet) -> u32 { + if self.data[index] != *target || visited.contains(&index) { + return 0; + } + visited.insert(index); + + let vertices = [ + ((0, -1), (1, 0)), + ((1, 0), (0, 1)), + ((0, 1), (-1, 0)), + ((-1, 0), (0, -1)) + ].iter() + .filter_map(|adjecent| self.get_vertex(index, target, *adjecent)) + .count() as u32; + + vertices + (DIRECTIONS.iter() + .filter_map(|direction| self.offset(&index, direction)) + .map(|pos| self.get_vertices(pos, target, visited)) + .sum::()) + } + + fn get_vertex(&self, index: usize, target: &char, adjacent: ((isize, isize), (isize, isize))) -> Option<(isize, isize)> { + let corner_offset = (adjacent.0.0 + adjacent.1.0, adjacent.0.1 + adjacent.1.1); + let is_corner_present = self.offset(&index, &corner_offset) + .map(|index| self.data[index]) + .is_some_and(|char| char == *target); + let is_first_present = self.offset(&index, &adjacent.0) + .map(|index| self.data[index]) + .is_some_and(|char| char == *target); + let is_second_present = self.offset(&index, &adjacent.1) + .map(|index| self.data[index]) + .is_some_and(|char| char == *target); + if (is_first_present && is_second_present && !is_corner_present) + || (!is_first_present && !is_second_present) { + let pos = self.index_to_xy(&index)?; + return Some((pos.0 as isize + corner_offset.0, pos.1 as isize + corner_offset.1)); + } + None + } + + fn next_unvisited(&self, mut current: usize, visited: &HashSet) -> usize { + while visited.contains(¤t) { + current += 1; + } + current + } + + fn offset(&self, index: &usize, direction: &(isize, isize)) -> Option { + let pos = self.index_to_xy(index)?; + let x = pos.0.checked_add_signed(direction.0)?; + let y = pos.1.checked_add_signed(direction.1)?; + self.xy_to_index((x, y)) + } + + fn xy_to_index(&self, pos: (usize, usize)) -> Option { + if (0..self.width).contains(&pos.0) && (0..self.height).contains(&pos.1) { + Some(pos.1 * self.width + pos.0) + } else { + None + } + } + + fn index_to_xy(&self, index: &usize) -> Option<(usize, usize)> { + if index < &(self.width * self.height) { + Some((index % self.width, index / self.width)) + } else { + None + } + } + +} + +pub fn part_one(input: &str) -> Option { + let grid = Grid::parse(input); + let len = grid.width * grid.height; + let mut index = 0; + let mut visited = HashSet::new(); + let mut sum = 0; + while index < len { + let mut area = HashSet::new(); + sum += grid.get_perimeter(index, &grid.data[index], &mut area) * area.len() as u32; + visited.extend(area); + index = grid.next_unvisited(index + 1, &visited); + } + Some(sum) +} + +pub fn part_two(input: &str) -> Option { + let grid = Grid::parse(input); + let len = grid.width * grid.height; + let mut index = 0; + let mut visited = HashSet::new(); + let mut sum = 0; + while index < len { + let mut area = HashSet::new(); + sum += grid.get_vertices(index, &grid.data[index], &mut area) * area.len() as u32; + visited.extend(area); + index = grid.next_unvisited(index + 1, &visited); + } + Some(sum) +} + +#[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(1930)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(1206)); + } +}