Skip to content

Commit de96827

Browse files
committed
Day 12
1 parent ff4db6f commit de96827

File tree

3 files changed

+178
-2
lines changed

3 files changed

+178
-2
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
3737
| [Day 8](./src/bin/08.rs) | `10.2µs` | `12.4µs` |
3838
| [Day 9](./src/bin/09.rs) | `2.9ms` | `49.1ms` |
3939
| [Day 10](./src/bin/10.rs) | `96.9µs` | `97.3µs` |
40-
| [Day 11](./src/bin/11.rs) | `216.1µs` | `10.9ms` |
40+
| [Day 11](./src/bin/11.rs) | `218.3µs` | `11.2ms` |
41+
| [Day 12](./src/bin/12.rs) | `3.0ms` | `2.8ms` |
4142

42-
**Total: 187.23ms**
43+
**Total: 193.33ms**
4344
<!--- benchmarking table --->
4445

4546
---

data/examples/12.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RRRRIICCFF
2+
RRRRIICCCF
3+
VVRRRCCFFF
4+
VVRCCCJFFF
5+
VVVVCJJCFE
6+
VVIVCCJJEE
7+
VVIIICJJEE
8+
MIIIIIJJEE
9+
MIIISIJEEE
10+
MMMISSJEEE

src/bin/12.rs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use std::collections::HashSet;
2+
use itertools::Itertools;
3+
4+
advent_of_code::solution!(12);
5+
6+
const DIRECTIONS: [(isize, isize); 4] = [
7+
(0, -1),
8+
(1, 0),
9+
(0, 1),
10+
(-1, 0),
11+
];
12+
13+
#[derive(Debug)]
14+
struct Grid {
15+
width: usize,
16+
height: usize,
17+
data: Vec<char>,
18+
}
19+
20+
impl Grid {
21+
fn parse(input: &str) -> Grid {
22+
let width = input.lines().next().unwrap_or_default().len();
23+
let data = input.lines()
24+
.flat_map(|line| line.chars())
25+
.collect_vec();
26+
let height = data.len() / width;
27+
Grid {
28+
height,
29+
width,
30+
data,
31+
}
32+
}
33+
34+
fn get_perimeter(&self, index: usize, target: &char, visited: &mut HashSet<usize>) -> u32 {
35+
if visited.contains(&index) {
36+
return 0;
37+
}
38+
if self.data[index] != *target {
39+
return 1;
40+
}
41+
visited.insert(index);
42+
DIRECTIONS.iter()
43+
.map(|direction| self.offset(&index, direction))
44+
.map(|pos| pos.map(|pos| self.get_perimeter(pos, target, visited)).unwrap_or(1))
45+
.sum()
46+
}
47+
48+
fn get_vertices(&self, index: usize, target: &char, visited: &mut HashSet<usize>) -> u32 {
49+
if self.data[index] != *target || visited.contains(&index) {
50+
return 0;
51+
}
52+
visited.insert(index);
53+
54+
let vertices = [
55+
((0, -1), (1, 0)),
56+
((1, 0), (0, 1)),
57+
((0, 1), (-1, 0)),
58+
((-1, 0), (0, -1))
59+
].iter()
60+
.filter_map(|adjecent| self.get_vertex(index, target, *adjecent))
61+
.count() as u32;
62+
63+
vertices + (DIRECTIONS.iter()
64+
.filter_map(|direction| self.offset(&index, direction))
65+
.map(|pos| self.get_vertices(pos, target, visited))
66+
.sum::<u32>())
67+
}
68+
69+
fn get_vertex(&self, index: usize, target: &char, adjacent: ((isize, isize), (isize, isize))) -> Option<(isize, isize)> {
70+
let corner_offset = (adjacent.0.0 + adjacent.1.0, adjacent.0.1 + adjacent.1.1);
71+
let is_corner_present = self.offset(&index, &corner_offset)
72+
.map(|index| self.data[index])
73+
.is_some_and(|char| char == *target);
74+
let is_first_present = self.offset(&index, &adjacent.0)
75+
.map(|index| self.data[index])
76+
.is_some_and(|char| char == *target);
77+
let is_second_present = self.offset(&index, &adjacent.1)
78+
.map(|index| self.data[index])
79+
.is_some_and(|char| char == *target);
80+
if (is_first_present && is_second_present && !is_corner_present)
81+
|| (!is_first_present && !is_second_present) {
82+
let pos = self.index_to_xy(&index)?;
83+
return Some((pos.0 as isize + corner_offset.0, pos.1 as isize + corner_offset.1));
84+
}
85+
None
86+
}
87+
88+
fn next_unvisited(&self, mut current: usize, visited: &HashSet<usize>) -> usize {
89+
while visited.contains(&current) {
90+
current += 1;
91+
}
92+
current
93+
}
94+
95+
fn offset(&self, index: &usize, direction: &(isize, isize)) -> Option<usize> {
96+
let pos = self.index_to_xy(index)?;
97+
let x = pos.0.checked_add_signed(direction.0)?;
98+
let y = pos.1.checked_add_signed(direction.1)?;
99+
self.xy_to_index((x, y))
100+
}
101+
102+
fn xy_to_index(&self, pos: (usize, usize)) -> Option<usize> {
103+
if (0..self.width).contains(&pos.0) && (0..self.height).contains(&pos.1) {
104+
Some(pos.1 * self.width + pos.0)
105+
} else {
106+
None
107+
}
108+
}
109+
110+
fn index_to_xy(&self, index: &usize) -> Option<(usize, usize)> {
111+
if index < &(self.width * self.height) {
112+
Some((index % self.width, index / self.width))
113+
} else {
114+
None
115+
}
116+
}
117+
118+
}
119+
120+
pub fn part_one(input: &str) -> Option<u32> {
121+
let grid = Grid::parse(input);
122+
let len = grid.width * grid.height;
123+
let mut index = 0;
124+
let mut visited = HashSet::new();
125+
let mut sum = 0;
126+
while index < len {
127+
let mut area = HashSet::new();
128+
sum += grid.get_perimeter(index, &grid.data[index], &mut area) * area.len() as u32;
129+
visited.extend(area);
130+
index = grid.next_unvisited(index + 1, &visited);
131+
}
132+
Some(sum)
133+
}
134+
135+
pub fn part_two(input: &str) -> Option<u32> {
136+
let grid = Grid::parse(input);
137+
let len = grid.width * grid.height;
138+
let mut index = 0;
139+
let mut visited = HashSet::new();
140+
let mut sum = 0;
141+
while index < len {
142+
let mut area = HashSet::new();
143+
sum += grid.get_vertices(index, &grid.data[index], &mut area) * area.len() as u32;
144+
visited.extend(area);
145+
index = grid.next_unvisited(index + 1, &visited);
146+
}
147+
Some(sum)
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
use super::*;
153+
154+
#[test]
155+
fn test_part_one() {
156+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
157+
assert_eq!(result, Some(1930));
158+
}
159+
160+
#[test]
161+
fn test_part_two() {
162+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
163+
assert_eq!(result, Some(1206));
164+
}
165+
}

0 commit comments

Comments
 (0)