feat(day12): init
This commit is contained in:
parent
bee665ddb4
commit
4f6729d15c
2 changed files with 207 additions and 0 deletions
205
src/solutions/day_12.rs
Normal file
205
src/solutions/day_12.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use std::collections::{HashSet, VecDeque};
|
||||
|
||||
use crate::common::{Answer, Solution};
|
||||
|
||||
pub struct Day12;
|
||||
|
||||
impl Solution for Day12 {
|
||||
fn name(&self) -> &'static str {
|
||||
"Garden Groups"
|
||||
}
|
||||
|
||||
fn part_a(&self, input: &str) -> Answer {
|
||||
let grid = input
|
||||
.lines()
|
||||
.map(|l| l.chars().collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut all_positions: HashSet<(usize, usize)> =
|
||||
HashSet::with_capacity(grid.len() * grid[0].len());
|
||||
let mut areas = Vec::new();
|
||||
|
||||
for x in 0..grid.len() {
|
||||
for y in 0..grid[0].len() {
|
||||
if !all_positions.contains(&(x, y)) {
|
||||
let result = flood_fill((x, y), &grid);
|
||||
all_positions.extend(result.iter());
|
||||
areas.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
areas
|
||||
.iter()
|
||||
.map(|positions| {
|
||||
let area = positions.len();
|
||||
|
||||
let perimeter = positions
|
||||
.iter()
|
||||
.map(|pos| {
|
||||
get_adjacent(*pos, &grid)
|
||||
.filter(|opt| match opt {
|
||||
Some(other_pos) => {
|
||||
grid[other_pos.0][other_pos.1] != grid[pos.0][pos.1]
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
.count()
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
area * perimeter
|
||||
})
|
||||
.sum::<usize>()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn part_b(&self, input: &str) -> Answer {
|
||||
let grid = input
|
||||
.lines()
|
||||
.map(|l| l.chars().collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut all_positions: HashSet<(usize, usize)> =
|
||||
HashSet::with_capacity(grid.len() * grid[0].len());
|
||||
let mut areas = Vec::new();
|
||||
|
||||
for x in 0..grid.len() {
|
||||
for y in 0..grid[0].len() {
|
||||
if !all_positions.contains(&(x, y)) {
|
||||
let result = flood_fill((x, y), &grid);
|
||||
all_positions.extend(result.iter());
|
||||
areas.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
areas
|
||||
.iter()
|
||||
.map(|positions| {
|
||||
let area = positions.len();
|
||||
|
||||
let sides = positions
|
||||
.iter()
|
||||
.map(|pos| right_angles(*pos, positions, &grid))
|
||||
.sum::<usize>();
|
||||
|
||||
area * sides
|
||||
})
|
||||
.sum::<usize>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn flood_fill(start: (usize, usize), grid: &[Vec<char>]) -> HashSet<(usize, usize)> {
|
||||
let letter = grid[start.0][start.1];
|
||||
|
||||
let mut positions = HashSet::new();
|
||||
let mut queue = VecDeque::from([start]);
|
||||
|
||||
while let Some(position) = queue.pop_front() {
|
||||
if !positions.contains(&position) && grid[position.0][position.1] == letter {
|
||||
positions.insert(position);
|
||||
queue.extend(get_adjacent(position, grid).flatten());
|
||||
}
|
||||
}
|
||||
|
||||
positions
|
||||
}
|
||||
|
||||
fn get_adjacent(
|
||||
pos: (usize, usize),
|
||||
grid: &[Vec<char>],
|
||||
) -> impl Iterator<Item = Option<(usize, usize)>> {
|
||||
let max_x = grid.len() - 1;
|
||||
let max_y = grid[0].len() - 1;
|
||||
|
||||
[
|
||||
pos.0.checked_sub(1).map(|n| (n, pos.1)),
|
||||
(pos.0 < max_x).then(|| (pos.0 + 1, pos.1)),
|
||||
pos.1.checked_sub(1).map(|n| (pos.0, n)),
|
||||
(pos.1 < max_y).then(|| (pos.0, pos.1 + 1)),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
fn right_angles(pos: (usize, usize), other: &HashSet<(usize, usize)>, grid: &[Vec<char>]) -> usize {
|
||||
let is_out = |o: Option<(usize, usize)>| o.filter(|p| other.contains(p)).is_none();
|
||||
|
||||
let x_bef = pos.0.checked_sub(1);
|
||||
let y_bef = pos.1.checked_sub(1);
|
||||
let x_aft = (pos.0 < grid.len() - 1).then_some(pos.0 + 1);
|
||||
let y_aft = (pos.1 < grid[0].len() - 1).then_some(pos.1 + 1);
|
||||
|
||||
// goes in a circle starting at (-1, -1)
|
||||
let mut around: VecDeque<_> = [
|
||||
x_bef.zip(y_bef),
|
||||
x_bef.map(|x| (x, pos.1)),
|
||||
x_bef.zip(y_aft),
|
||||
y_aft.map(|y| (pos.0, y)),
|
||||
x_aft.zip(y_aft),
|
||||
x_aft.map(|x| (x, pos.1)),
|
||||
x_aft.zip(y_bef),
|
||||
y_bef.map(|y| (pos.0, y)),
|
||||
]
|
||||
.into();
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
for _ in 0..4 {
|
||||
let back = *around.back().unwrap();
|
||||
|
||||
if (is_out(around[1]) && is_out(back))
|
||||
|| (is_out(around[0]) && !is_out(around[1]) && !is_out(back))
|
||||
{
|
||||
count += 1;
|
||||
}
|
||||
|
||||
let front = (around.pop_front().unwrap(), around.pop_front().unwrap());
|
||||
around.push_back(front.0);
|
||||
around.push_back(front.1);
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Day12;
|
||||
use crate::common::Solution;
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
const INPUT: &str = indoc! {"
|
||||
RRRRIICCFF
|
||||
RRRRIICCCF
|
||||
VVRRRCCFFF
|
||||
VVRCCCJFFF
|
||||
VVVVCJJCFE
|
||||
VVIVCCJJEE
|
||||
VVIIICJJEE
|
||||
MIIIIIJJEE
|
||||
MIIISIJEEE
|
||||
MMMISSJEEE
|
||||
"};
|
||||
|
||||
const INPUT_B: &str = indoc! {"
|
||||
AAAAAA
|
||||
AAABBA
|
||||
AAABBA
|
||||
ABBAAA
|
||||
ABBAAA
|
||||
AAAAAA
|
||||
"};
|
||||
|
||||
#[test]
|
||||
fn part_a() {
|
||||
assert_eq!(Day12.part_a(INPUT), 1930.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part_b() {
|
||||
// assert_eq!(Day12.part_b(INPUT), 1206.into());
|
||||
assert_eq!(Day12.part_b(INPUT_B), 368.into());
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ mod day_08;
|
|||
mod day_09;
|
||||
mod day_10;
|
||||
mod day_11;
|
||||
mod day_12;
|
||||
|
||||
pub const SOLUTIONS: &[&dyn Solution] = &[
|
||||
&day_01::Day01,
|
||||
|
@ -24,4 +25,5 @@ pub const SOLUTIONS: &[&dyn Solution] = &[
|
|||
&day_09::Day09,
|
||||
&day_10::Day10,
|
||||
&day_11::Day11,
|
||||
&day_12::Day12,
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue