Skip to content

Commit

Permalink
[2024] Day 12 initial solution
Browse files Browse the repository at this point in the history
what a mess...
  • Loading branch information
connorslade committed Dec 12, 2024
1 parent d02e4c0 commit 1e4686e
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even
- [Day 09: Disk Fragmenter](aoc_2024/src/day_09.rs)
- [Day 10: Hoof It](aoc_2024/src/day_10.rs)
- [Day 11: Plutonian Pebbles](aoc_2024/src/day_11.rs)
- [Day 12: Garden Groups](aoc_2024/src/day_12.rs)
<!-- MARKER -->

## [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)
Expand Down
4 changes: 2 additions & 2 deletions aoc_2024/src/day_11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ fn parse(input: &str) -> Vec<u64> {

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

Expand Down
188 changes: 188 additions & 0 deletions aoc_2024/src/day_12.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use std::{collections::HashSet, convert::identity};

use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
use common::{solution, Answer};
use nd_vec::Vec2;

solution!("Garden Groups", 12);

fn part_a(input: &str) -> Answer {
let mut garden = Garden::parse(input);

let mut sum = 0;

for pos in garden.matrix.clone().iter().map(|(pos, _)| pos) {
let (area, perimeter) = garden.flood(pos);
sum += area * perimeter;
}

sum.into()
}

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

let mut sum = 0;

for pos in garden.matrix.clone().iter().map(|(pos, _)| pos) {
let plant = *garden.matrix.get(pos).unwrap();
let (area, perimeter) = garden.flood_b(pos);
if perimeter.is_empty() {
continue;
}

let mut corners = 0;

for &point in area.iter() {
for (a, b) in [
(Direction::Up, Direction::Right),
(Direction::Right, Direction::Down),
(Direction::Down, Direction::Left),
(Direction::Left, Direction::Up),
] {
// if a and b are both not in area +1
if !area.contains(&a.advance(point)) && !area.contains(&b.advance(point)) {
corners += 1;
}
}

for (a, b) in [
(Direction::Up, Direction::Right),
(Direction::Right, Direction::Down),
(Direction::Down, Direction::Left),
(Direction::Left, Direction::Up),
] {
let e = a.as_vector::<i32>() + b.as_vector();
if area.contains(&a.advance(point))
&& area.contains(&b.advance(point))
&& !area.contains(&(point + e))
{
corners += 1;
}
}
}

println!("{} * {corners} [{plant}]", area.len());
sum += area.len() * corners;
}

sum.into()
}

struct Garden {
matrix: Matrix<char>,

seen: HashSet<Vec2<usize>>,
}

impl Garden {
fn parse(input: &str) -> Self {
let matrix = Matrix::new_chars(input, identity);
Self {
matrix,
seen: HashSet::new(),
}
}

// -> (area, perimeter)
fn flood(&mut self, start: Vec2<usize>) -> (u32, u32) {
let (mut area, mut perimeter) = (1, 0);
let plant = self.matrix.get(start).unwrap();

let mut queue = Vec::new();

if !self.seen.insert(start) {
return (0, 0);
}
queue.push(start);

while let Some(pos) = queue.pop() {
for dir in Direction::ALL.into_iter() {
let Some(next) = dir.try_advance(pos) else {
perimeter += 1;
continue;
};
if !self.matrix.contains(next) {
perimeter += 1;
continue;
}

if self.matrix.get(next).unwrap() == plant {
if self.seen.insert(next) {
area += 1;
queue.push(next);
}
} else {
perimeter += 1
}
}
}

(area, perimeter)
}

fn flood_b(&mut self, start: Vec2<usize>) -> (HashSet<Vec2<i32>>, HashSet<Vec2<i32>>) {
let (mut area, mut perimeter) = (HashSet::new(), HashSet::new());
area.insert(start.try_cast::<i32>().unwrap());
let plant = self.matrix.get(start).unwrap();

let mut queue = Vec::new();

if !self.seen.insert(start) {
return (HashSet::new(), HashSet::new());
}
queue.push(start);

while let Some(pos) = queue.pop() {
for dir in Direction::ALL.into_iter() {
let Some(next) = dir.try_advance(pos) else {
perimeter.insert(dir.advance(pos.try_cast::<i32>().unwrap()));
continue;
};
if !self.matrix.contains(next) {
perimeter.insert(next.try_cast::<i32>().unwrap());
continue;
}

if self.matrix.get(next).unwrap() == plant {
if self.seen.insert(next) {
area.insert(next.try_cast::<i32>().unwrap());
queue.push(next);
}
} else {
perimeter.insert(next.try_cast::<i32>().unwrap());
}
}
}

(area, perimeter)
}
}

#[cfg(test)]
mod test {
use indoc::indoc;

const CASE: &str = indoc! {"
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
"};

#[test]
fn part_a() {
assert_eq!(super::part_a(CASE), 1930.into());
}

#[test]
fn part_b() {
assert_eq!(super::part_b(CASE), 1206.into());
}
}
2 changes: 2 additions & 0 deletions aoc_2024/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod day_08;
mod day_09;
mod day_10;
mod day_11;
mod day_12;
// [import_marker]

pub const SOLUTIONS: &[Solution] = &[
Expand All @@ -25,5 +26,6 @@ pub const SOLUTIONS: &[Solution] = &[
day_09::SOLUTION,
day_10::SOLUTION,
day_11::SOLUTION,
day_12::SOLUTION,
// [list_marker]
];
2 changes: 1 addition & 1 deletion aoc_lib/src/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl<T> Matrix<T> {
pos.x() < self.size.x() && pos.y() < self.size.y()
}

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

0 comments on commit 1e4686e

Please sign in to comment.