Skip to content

Commit 1e4686e

Browse files
committed
[2024] Day 12 initial solution
what a mess...
1 parent d02e4c0 commit 1e4686e

File tree

5 files changed

+194
-3
lines changed

5 files changed

+194
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even
1616
- [Day 09: Disk Fragmenter](aoc_2024/src/day_09.rs)
1717
- [Day 10: Hoof It](aoc_2024/src/day_10.rs)
1818
- [Day 11: Plutonian Pebbles](aoc_2024/src/day_11.rs)
19+
- [Day 12: Garden Groups](aoc_2024/src/day_12.rs)
1920
<!-- MARKER -->
2021

2122
## [2023](https://adventofcode.com/2023) [![aoc_2023](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml/badge.svg)](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml)

aoc_2024/src/day_11.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ fn parse(input: &str) -> Vec<u64> {
4747

4848
/// Given an integer, this function will return None if it has an odd number of
4949
/// base 10 digits, otherwise the first half and second half of the digits will
50-
/// be returned severalty.
50+
/// be returned separately.
5151
fn split_digits(num: u64) -> Option<(u64, u64)> {
5252
let digits = num.ilog10() + 1;
53-
let pow = 10_u64.pow(digits / 2);
53+
let pow = u64::pow(10, digits / 2);
5454
(digits & 1 == 0).then(|| (num / pow, num % pow))
5555
}
5656

aoc_2024/src/day_12.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::{collections::HashSet, convert::identity};
2+
3+
use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
4+
use common::{solution, Answer};
5+
use nd_vec::Vec2;
6+
7+
solution!("Garden Groups", 12);
8+
9+
fn part_a(input: &str) -> Answer {
10+
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()
20+
}
21+
22+
fn part_b(input: &str) -> Answer {
23+
let mut garden = Garden::parse(input);
24+
25+
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+
34+
let mut corners = 0;
35+
36+
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+
}
47+
}
48+
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+
}
62+
}
63+
}
64+
65+
println!("{} * {corners} [{plant}]", area.len());
66+
sum += area.len() * corners;
67+
}
68+
69+
sum.into()
70+
}
71+
72+
struct Garden {
73+
matrix: Matrix<char>,
74+
75+
seen: HashSet<Vec2<usize>>,
76+
}
77+
78+
impl Garden {
79+
fn parse(input: &str) -> Self {
80+
let matrix = Matrix::new_chars(input, identity);
81+
Self {
82+
matrix,
83+
seen: HashSet::new(),
84+
}
85+
}
86+
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();
93+
94+
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+
}
119+
}
120+
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();
128+
129+
let mut queue = Vec::new();
130+
131+
if !self.seen.insert(start) {
132+
return (HashSet::new(), HashSet::new());
133+
}
134+
queue.push(start);
135+
136+
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+
};
142+
if !self.matrix.contains(next) {
143+
perimeter.insert(next.try_cast::<i32>().unwrap());
144+
continue;
145+
}
146+
147+
if self.matrix.get(next).unwrap() == plant {
148+
if self.seen.insert(next) {
149+
area.insert(next.try_cast::<i32>().unwrap());
150+
queue.push(next);
151+
}
152+
} else {
153+
perimeter.insert(next.try_cast::<i32>().unwrap());
154+
}
155+
}
156+
}
157+
158+
(area, perimeter)
159+
}
160+
}
161+
162+
#[cfg(test)]
163+
mod test {
164+
use indoc::indoc;
165+
166+
const CASE: &str = indoc! {"
167+
RRRRIICCFF
168+
RRRRIICCCF
169+
VVRRRCCFFF
170+
VVRCCCJFFF
171+
VVVVCJJCFE
172+
VVIVCCJJEE
173+
VVIIICJJEE
174+
MIIIIIJJEE
175+
MIIISIJEEE
176+
MMMISSJEEE
177+
"};
178+
179+
#[test]
180+
fn part_a() {
181+
assert_eq!(super::part_a(CASE), 1930.into());
182+
}
183+
184+
#[test]
185+
fn part_b() {
186+
assert_eq!(super::part_b(CASE), 1206.into());
187+
}
188+
}

aoc_2024/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod day_08;
1111
mod day_09;
1212
mod day_10;
1313
mod day_11;
14+
mod day_12;
1415
// [import_marker]
1516

1617
pub const SOLUTIONS: &[Solution] = &[
@@ -25,5 +26,6 @@ pub const SOLUTIONS: &[Solution] = &[
2526
day_09::SOLUTION,
2627
day_10::SOLUTION,
2728
day_11::SOLUTION,
29+
day_12::SOLUTION,
2830
// [list_marker]
2931
];

aoc_lib/src/matrix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl<T> Matrix<T> {
4343
pos.x() < self.size.x() && pos.y() < self.size.y()
4444
}
4545

46-
pub fn iter(&self) -> impl Iterator<Item = (Vec2<usize>, &T)> {
46+
pub fn iter(&self) -> impl Iterator<Item = (Vec2<usize>, &T)> {
4747
(0..self.data.len()).map(|x| {
4848
let pos = vector!(x % self.size.x(), x / self.size.x());
4949
let data = self.get(pos).unwrap();

0 commit comments

Comments
 (0)