Skip to content

Commit 02e89a9

Browse files
committed
Day 12
1 parent edc6a5f commit 02e89a9

File tree

4 files changed

+278
-1
lines changed

4 files changed

+278
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2222
| [Day 9](./src/bin/09.rs) | `149.0µs` | `6.5ms` |
2323
| [Day 10](./src/bin/10.rs) | `31.1µs` | `28.6µs` |
2424
| [Day 11](./src/bin/11.rs) | `318.0ns` | `292.0ns` |
25+
| [Day 12](./src/bin/12.rs) | `434.7µs` | `674.6µs` |
2526

26-
**Total: 12.71ms**
27+
**Total: 13.82ms**
2728
<!--- benchmarking table --->
2829

2930
---

data/examples/12.txt

Lines changed: 10 additions & 0 deletions
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

mygrid/src/grid.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ impl<T> Grid<T> {
203203
.map(move |(i, t)| (Point::new_usize(i / self.width, i % self.width), t))
204204
}
205205

206+
#[inline]
207+
pub fn iter_positions(&self) -> impl Iterator<Item = Point> {
208+
let width = self.width as isize;
209+
let height = self.height as isize;
210+
(0..height).flat_map(move |line| (0..width).map(move |column| Point::new(line, column)))
211+
}
212+
206213
#[inline]
207214
pub fn get_item(&self, point: Point) -> Option<&T> {
208215
if self.is_in_bounds(point) {
@@ -249,6 +256,7 @@ impl<T> Grid<T> {
249256

250257
impl<T: std::fmt::Display> std::fmt::Display for Grid<T> {
251258
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259+
writeln!(f, "({}, {})", self.width, self.height)?;
252260
for r in 0..self.height {
253261
for c in 0..self.width {
254262
let point = Point::new_usize(r, c);
@@ -262,6 +270,7 @@ impl<T: std::fmt::Display> std::fmt::Display for Grid<T> {
262270

263271
impl<T: std::fmt::Display> std::fmt::Debug for Grid<T> {
264272
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273+
writeln!(f, "({}, {})", self.width, self.height)?;
265274
write!(f, "{}", self)
266275
}
267276
}
@@ -276,6 +285,34 @@ impl<T: PartialEq> Grid<T> {
276285
}
277286
}
278287

288+
impl<T: Clone> Grid<T> {
289+
#[inline]
290+
pub fn map<N: Default + Copy>(&self, f: impl Fn(Point, &T) -> N) -> Grid<N> {
291+
let mut new_grid = Grid::new(self.width, self.height, N::default());
292+
for point in self.iter_positions() {
293+
new_grid[point] = f(point, &self[point]);
294+
}
295+
new_grid
296+
}
297+
}
298+
299+
impl Grid<bool> {
300+
#[inline]
301+
pub fn to_debug(&self) -> Grid<char> {
302+
self.map(|_, &b| if b { '#' } else { '.' })
303+
}
304+
305+
#[inline]
306+
pub fn is_true(&self, point: Point) -> bool {
307+
self.is_in_bounds(point) && self[point]
308+
}
309+
310+
#[inline]
311+
pub fn is_false(&self, point: Point) -> bool {
312+
!self.is_in_bounds(point) || !self[point]
313+
}
314+
}
315+
279316
#[cfg(test)]
280317
mod tests {
281318
use super::*;

src/bin/12.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use heapless::Vec as HeaplessVec;
2+
use std::collections::VecDeque;
3+
4+
use mygrid::{direction::ORTHOGONAL, grid::Grid};
5+
6+
advent_of_code::solution!(12);
7+
8+
pub fn part_one(input: &str) -> Option<u32> {
9+
let grid = Grid::new_char_grid_from_str(input);
10+
let mut visited = Grid::new(grid.width, grid.height, false);
11+
let base_region = visited.clone();
12+
13+
let mut res: u32 = 0;
14+
15+
for pos in grid.iter_positions() {
16+
if visited[pos] {
17+
continue;
18+
}
19+
20+
let mut region_perimeter = 0;
21+
let mut region_area = 0;
22+
let plant = grid[pos];
23+
let mut region = base_region.clone();
24+
let mut q = VecDeque::with_capacity(100);
25+
q.push_back(pos);
26+
while let Some(current_pos) = q.pop_front() {
27+
if region[current_pos] {
28+
continue;
29+
}
30+
31+
region[current_pos] = true;
32+
visited[current_pos] = true;
33+
34+
region_area += 1;
35+
36+
let positions: HeaplessVec<_, 4> = ORTHOGONAL
37+
.iter()
38+
.map(|&d| current_pos + d)
39+
.filter(|&n| grid.is_in_bounds(n))
40+
.filter(|&n| grid[n] == plant)
41+
.collect();
42+
43+
let region_neighbors = positions.iter().filter(|&&n| region[n]).count() as isize;
44+
region_perimeter += -region_neighbors + (4 - region_neighbors);
45+
46+
for neighbor in positions.iter().filter(|&&n| !region[n]) {
47+
q.push_back(*neighbor);
48+
}
49+
}
50+
51+
let res_price = region_area * region_perimeter;
52+
res += res_price as u32;
53+
}
54+
55+
Some(res)
56+
}
57+
58+
pub fn part_two(input: &str) -> Option<u32> {
59+
let grid = Grid::new_char_grid_from_str(input);
60+
let mut visited = Grid::new(grid.width, grid.height, false);
61+
let base_region = visited.clone();
62+
63+
let mut res: u32 = 0;
64+
65+
for pos in grid.iter_positions() {
66+
if visited[pos] {
67+
continue;
68+
}
69+
// dbg!("================================================================");
70+
// dbg!(&pos, &grid[pos]);
71+
// dbg!("================================================================");
72+
73+
let mut region_sides = 0;
74+
let mut region_area = 0;
75+
let plant = grid[pos];
76+
let mut region = base_region.clone();
77+
let mut q = VecDeque::with_capacity(100);
78+
q.push_back(pos);
79+
while let Some(current_pos) = q.pop_front() {
80+
if region[current_pos] {
81+
continue;
82+
}
83+
84+
region[current_pos] = true;
85+
visited[current_pos] = true;
86+
87+
region_area += 1;
88+
89+
// dbg!(&region.to_debug());
90+
91+
let mut sides_diff = 0;
92+
for &d in ORTHOGONAL.iter() {
93+
let n = current_pos + d;
94+
let in_region = region.is_true(n);
95+
if in_region {
96+
let mut removes_side = true;
97+
let n_1 = current_pos + d.rotate_clockwise();
98+
let d_1 = n_1 + d;
99+
let n_1_keep_side = region.is_false(n_1) && region.is_true(d_1);
100+
101+
let n_2 = current_pos + d.rotate_counterclockwise();
102+
let d_2 = n_2 + d;
103+
let n_2_keep_side = region.is_false(n_2) && region.is_true(d_2);
104+
if n_1_keep_side || n_2_keep_side {
105+
removes_side = false;
106+
}
107+
108+
if removes_side {
109+
sides_diff -= 1;
110+
// dbg!("removes side", &n, &d, &n_1_keep_side, &n_2_keep_side);
111+
} else {
112+
// dbg!("not removing side", &n, &d, &n_1_keep_side, &n_2_keep_side);
113+
}
114+
115+
// add 1 if we break a side
116+
let splits_existing_side = region.is_false(n_1)
117+
&& region.is_false(n_2)
118+
&& region.is_true(d_1)
119+
&& region.is_true(d_2);
120+
if splits_existing_side {
121+
sides_diff += 1;
122+
//dbg!("splits existing side", &n, &d);
123+
}
124+
} else {
125+
let mut adds_side = true;
126+
let n_1 = current_pos + d.rotate_clockwise();
127+
let d_1 = n_1 + d;
128+
let n_1_has_same_side = region.is_true(n_1) && region.is_false(d_1);
129+
130+
let n_2 = current_pos + d.rotate_counterclockwise();
131+
let d_2 = n_2 + d;
132+
let n_2_has_same_side = region.is_true(n_2) && region.is_false(d_2);
133+
134+
if n_1_has_same_side || n_2_has_same_side {
135+
adds_side = false;
136+
}
137+
138+
if adds_side {
139+
sides_diff += 1;
140+
//dbg!("adds side", &n, &d, &n_1_has_same_side, &n_2_has_same_side);
141+
} else {
142+
// dbg!(
143+
// "not adding side",
144+
// &n,
145+
// &d,
146+
// &n_1_has_same_side,
147+
// &n_2_has_same_side
148+
// );
149+
}
150+
151+
let merges_existing_sides = region.is_true(n_1)
152+
&& region.is_true(n_2)
153+
&& region.is_false(d_1)
154+
&& region.is_false(d_2);
155+
if merges_existing_sides {
156+
sides_diff -= 1;
157+
}
158+
}
159+
}
160+
161+
region_sides += sides_diff;
162+
//dbg!(&region_sides, sides_diff);
163+
164+
for neighbor in ORTHOGONAL
165+
.iter()
166+
.map(|&d| current_pos + d)
167+
.filter(|&n| grid.is_in_bounds(n))
168+
.filter(|&n| grid[n] == plant)
169+
.filter(|&n| !region[n])
170+
{
171+
q.push_back(neighbor);
172+
}
173+
}
174+
175+
let res_price = region_area * region_sides;
176+
177+
// dbg!(pos, plant, &region_area, &region_sides, &res_price);
178+
res += res_price as u32;
179+
}
180+
181+
Some(res)
182+
}
183+
184+
#[cfg(test)]
185+
mod tests {
186+
use super::*;
187+
188+
#[test]
189+
fn test_part_one_1() {
190+
let input = "AAAA\nBBCD\nBBCC\nEEEC\n";
191+
192+
let result = part_one(&input);
193+
assert_eq!(result, Some(10 * 4 + 10 * 4 + 8 * 4 + 8 * 3 + 4 * 1));
194+
}
195+
196+
#[test]
197+
fn test_part_one_2() {
198+
let input = advent_of_code::template::read_file("examples", DAY);
199+
let result = part_one(&input);
200+
assert_eq!(result, Some(1930));
201+
}
202+
203+
#[test]
204+
fn test_part_two_1() {
205+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
206+
assert_eq!(result, Some(1206));
207+
}
208+
209+
#[test]
210+
fn test_part_two_2() {
211+
let input = "AAAAAA\nAAABBA\nAAABBA\nABBAAA\nABBAAA\nAAAAAA\n";
212+
let result = part_two(&input);
213+
assert_eq!(result, Some(368));
214+
}
215+
216+
#[test]
217+
fn test_part_two_3() {
218+
let input = "EEEEE\nEXXXX\nEEEEE\nEXXXX\nEEEEE\n";
219+
let result = part_two(&input);
220+
assert_eq!(result, Some(236));
221+
}
222+
223+
#[test]
224+
fn test_part_two_4() {
225+
let input = "OOOOO\nOXOXO\nOOOOO\nOXOXO\nOOOOO\n";
226+
let result = part_two(&input);
227+
assert_eq!(result, Some(436));
228+
}
229+
}

0 commit comments

Comments
 (0)