Skip to content

Commit 708ed65

Browse files
committed
[2024] Cleanup day 12
1 parent 1e4686e commit 708ed65

File tree

2 files changed

+51
-93
lines changed

2 files changed

+51
-93
lines changed

aoc_2024/src/day_12.rs

Lines changed: 41 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,49 @@ use std::{collections::HashSet, convert::identity};
22

33
use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
44
use common::{solution, Answer};
5-
use nd_vec::Vec2;
5+
use itertools::Itertools;
6+
use nd_vec::{vector, Vec2};
67

78
solution!("Garden Groups", 12);
89

910
fn part_a(input: &str) -> Answer {
1011
let mut garden = Garden::parse(input);
11-
12-
let mut sum = 0;
13-
14-
for pos in garden.matrix.clone().iter().map(|(pos, _)| pos) {
15-
let (area, perimeter) = garden.flood(pos);
16-
sum += area * perimeter;
17-
}
18-
19-
sum.into()
12+
garden
13+
.points()
14+
.filter_map(|x| garden.flood(x))
15+
.map(|(area, perimeter)| area.len() * perimeter)
16+
.sum::<usize>()
17+
.into()
2018
}
2119

2220
fn part_b(input: &str) -> Answer {
2321
let mut garden = Garden::parse(input);
2422

2523
let mut sum = 0;
26-
27-
for pos in garden.matrix.clone().iter().map(|(pos, _)| pos) {
28-
let plant = *garden.matrix.get(pos).unwrap();
29-
let (area, perimeter) = garden.flood_b(pos);
30-
if perimeter.is_empty() {
31-
continue;
32-
}
33-
24+
for (area, _) in garden.points().filter_map(|x| garden.flood(x)) {
3425
let mut corners = 0;
3526

3627
for &point in area.iter() {
37-
for (a, b) in [
38-
(Direction::Up, Direction::Right),
39-
(Direction::Right, Direction::Down),
40-
(Direction::Down, Direction::Left),
41-
(Direction::Left, Direction::Up),
42-
] {
43-
// if a and b are both not in area +1
44-
if !area.contains(&a.advance(point)) && !area.contains(&b.advance(point)) {
45-
corners += 1;
46-
}
28+
// Count convex corners by checking to see that the wall is not in
29+
// any cardinal direction and a direction orthogonal to that
30+
for a in Direction::ALL {
31+
corners += (!area.contains(&a.wrapping_advance(point))
32+
&& !area.contains(&a.turn_right().wrapping_advance(point)))
33+
as u32;
4734
}
4835

49-
for (a, b) in [
50-
(Direction::Up, Direction::Right),
51-
(Direction::Right, Direction::Down),
52-
(Direction::Down, Direction::Left),
53-
(Direction::Left, Direction::Up),
54-
] {
55-
let e = a.as_vector::<i32>() + b.as_vector();
56-
if area.contains(&a.advance(point))
57-
&& area.contains(&b.advance(point))
58-
&& !area.contains(&(point + e))
59-
{
60-
corners += 1;
61-
}
36+
// Count the concave angles by looking for when both the orthogonal
37+
// directions are in the area, but not the diagonal between them.
38+
for a in Direction::ALL {
39+
let b = a.turn_right();
40+
corners += (area.contains(&a.wrapping_advance(point))
41+
&& area.contains(&b.wrapping_advance(point))
42+
&& !area.contains(&b.wrapping_advance(a.wrapping_advance(point))))
43+
as u32;
6244
}
6345
}
6446

65-
println!("{} * {corners} [{plant}]", area.len());
66-
sum += area.len() * corners;
47+
sum += area.len() as u32 * corners;
6748
}
6849

6950
sum.into()
@@ -84,78 +65,46 @@ impl Garden {
8465
}
8566
}
8667

87-
// -> (area, perimeter)
88-
fn flood(&mut self, start: Vec2<usize>) -> (u32, u32) {
89-
let (mut area, mut perimeter) = (1, 0);
90-
let plant = self.matrix.get(start).unwrap();
91-
92-
let mut queue = Vec::new();
68+
fn points(&self) -> impl Iterator<Item = Vec2<usize>> {
69+
let size = self.matrix.size;
70+
(0..size.x())
71+
.cartesian_product(0..size.y())
72+
.map(|(x, y)| vector!(x, y))
73+
}
9374

75+
fn flood(&mut self, start: Vec2<usize>) -> Option<(HashSet<Vec2<usize>>, usize)> {
9476
if !self.seen.insert(start) {
95-
return (0, 0);
96-
}
97-
queue.push(start);
98-
99-
while let Some(pos) = queue.pop() {
100-
for dir in Direction::ALL.into_iter() {
101-
let Some(next) = dir.try_advance(pos) else {
102-
perimeter += 1;
103-
continue;
104-
};
105-
if !self.matrix.contains(next) {
106-
perimeter += 1;
107-
continue;
108-
}
109-
110-
if self.matrix.get(next).unwrap() == plant {
111-
if self.seen.insert(next) {
112-
area += 1;
113-
queue.push(next);
114-
}
115-
} else {
116-
perimeter += 1
117-
}
118-
}
77+
return None;
11978
}
12079

121-
(area, perimeter)
122-
}
123-
124-
fn flood_b(&mut self, start: Vec2<usize>) -> (HashSet<Vec2<i32>>, HashSet<Vec2<i32>>) {
125-
let (mut area, mut perimeter) = (HashSet::new(), HashSet::new());
126-
area.insert(start.try_cast::<i32>().unwrap());
127-
let plant = self.matrix.get(start).unwrap();
80+
let mut area = HashSet::new();
81+
let mut perimeter = 0;
12882

12983
let mut queue = Vec::new();
84+
let plant = self.matrix.get(start).unwrap();
13085

131-
if !self.seen.insert(start) {
132-
return (HashSet::new(), HashSet::new());
133-
}
86+
area.insert(start);
13487
queue.push(start);
13588

13689
while let Some(pos) = queue.pop() {
137-
for dir in Direction::ALL.into_iter() {
138-
let Some(next) = dir.try_advance(pos) else {
139-
perimeter.insert(dir.advance(pos.try_cast::<i32>().unwrap()));
140-
continue;
141-
};
90+
for next in Direction::ALL.map(|x| x.wrapping_advance(pos)) {
14291
if !self.matrix.contains(next) {
143-
perimeter.insert(next.try_cast::<i32>().unwrap());
92+
perimeter += 1;
14493
continue;
14594
}
14695

14796
if self.matrix.get(next).unwrap() == plant {
14897
if self.seen.insert(next) {
149-
area.insert(next.try_cast::<i32>().unwrap());
98+
area.insert(next);
15099
queue.push(next);
151100
}
152101
} else {
153-
perimeter.insert(next.try_cast::<i32>().unwrap());
102+
perimeter += 1;
154103
}
155104
}
156105
}
157106

158-
(area, perimeter)
107+
Some((area, perimeter))
159108
}
160109
}
161110

aoc_lib/src/direction/cardinal.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use nd_vec::{vector, Vec2};
2-
use num_traits::{Num, Signed};
2+
use num_traits::{Num, Signed, WrappingSub};
33

44
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55
pub enum Direction {
@@ -48,6 +48,15 @@ impl Direction {
4848
}
4949
}
5050

51+
pub fn wrapping_advance<T: Num + WrappingSub + Copy>(&self, pos: Vec2<T>) -> Vec2<T> {
52+
match self {
53+
Self::Up => vector!(pos.x(), pos.y().wrapping_sub(&T::one())),
54+
Self::Down => vector!(pos.x(), pos.y() + T::one()),
55+
Self::Left => vector!(pos.x().wrapping_sub(&T::one()), pos.y()),
56+
Self::Right => vector!(pos.x() + T::one(), pos.y()),
57+
}
58+
}
59+
5160
pub fn opposite(&self) -> Self {
5261
match self {
5362
Self::Up => Self::Down,

0 commit comments

Comments
 (0)