day12: complete solution
This commit is contained in:
parent
31eb500832
commit
a56fc933c9
169
src/day12.rs
Normal file
169
src/day12.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
|
use grid::{Coord2d, Grid};
|
||||||
|
|
||||||
|
pub struct Farm {
|
||||||
|
map: Grid<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for Farm {
|
||||||
|
fn from(input: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
map: Grid::from(input.lines()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Farm {
|
||||||
|
fn compute_region(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
|
||||||
|
let our_plant = self.map.get(pos).unwrap();
|
||||||
|
let mut perimeter = 0;
|
||||||
|
let mut area = 1;
|
||||||
|
|
||||||
|
visited.set(pos, true);
|
||||||
|
|
||||||
|
for adj in [(-1i64, 0i64), (1, 0), (0, -1), (0, 1)].map(|ofs| pos + ofs) {
|
||||||
|
match self.map.get(&adj) {
|
||||||
|
Some(plant) if plant == our_plant => {
|
||||||
|
if visited.get(&adj) == Some(false) {
|
||||||
|
// add the perimeter of the growth from there if not visited yet
|
||||||
|
let (n_area, n_perimeter) = self.compute_region(&adj, visited);
|
||||||
|
area += n_area;
|
||||||
|
perimeter += n_perimeter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) | None => perimeter += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(area, perimeter)
|
||||||
|
}
|
||||||
|
fn regions_cost(&self) -> u64 {
|
||||||
|
let mut visited = Grid::with_shape(self.map.width(), self.map.height(), false);
|
||||||
|
let mut cost = 0;
|
||||||
|
for y in 0..self.map.height() {
|
||||||
|
for x in 0..self.map.width() {
|
||||||
|
cost += match visited.get(&(x, y)) {
|
||||||
|
Some(false) => {
|
||||||
|
let (area, perim) = self.compute_region(
|
||||||
|
&Coord2d {
|
||||||
|
x: x as i64,
|
||||||
|
y: y as i64,
|
||||||
|
},
|
||||||
|
&mut visited,
|
||||||
|
);
|
||||||
|
area * perim
|
||||||
|
}
|
||||||
|
Some(_) | None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cost
|
||||||
|
}
|
||||||
|
fn count_corners(&self, pos: &Coord2d) -> u64 {
|
||||||
|
// A
|
||||||
|
// AAA has 4 inside corners (pos at centre). check that AA A's exist and B doesn't for each rotation
|
||||||
|
// A AB
|
||||||
|
let our_plant = self.map.get(pos);
|
||||||
|
let inside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
|
||||||
|
.iter()
|
||||||
|
.filter(|inside_corner| {
|
||||||
|
self.map.get(&(pos + **inside_corner)) != our_plant
|
||||||
|
&& self.map.get(&(pos + (inside_corner.0, 0))) == our_plant
|
||||||
|
&& self.map.get(&(pos + (0, inside_corner.1))) == our_plant
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
let outside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
|
||||||
|
.iter()
|
||||||
|
.filter(|outside_corner| {
|
||||||
|
self.map.get(&(pos + (outside_corner.0, 0))) != our_plant
|
||||||
|
&& self.map.get(&(pos + (0, outside_corner.1))) != our_plant
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
(inside_corners + outside_corners) as u64
|
||||||
|
}
|
||||||
|
fn region_corners(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
|
||||||
|
let our_plant = self.map.get(pos).unwrap();
|
||||||
|
let mut area = 1;
|
||||||
|
let mut corners = self.count_corners(pos);
|
||||||
|
|
||||||
|
visited.set(pos, true);
|
||||||
|
|
||||||
|
for adj in [(-1i64, 0i64), (1, 0), (0, -1), (0, 1)].map(|ofs| pos + ofs) {
|
||||||
|
match self.map.get(&adj) {
|
||||||
|
Some(plant) if plant == our_plant => {
|
||||||
|
if visited.get(&adj) == Some(false) {
|
||||||
|
// add the perimeter of the growth from there if not visited yet
|
||||||
|
let (n_area, n_corners) = self.region_corners(&adj, visited);
|
||||||
|
area += n_area;
|
||||||
|
corners += n_corners;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) | None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(area, corners)
|
||||||
|
}
|
||||||
|
fn regions_discount_cost(&self) -> u64 {
|
||||||
|
let mut visited = Grid::with_shape(self.map.width(), self.map.height(), false);
|
||||||
|
let mut cost = 0;
|
||||||
|
for y in 0..self.map.height() {
|
||||||
|
for x in 0..self.map.width() {
|
||||||
|
cost += match visited.get(&(x, y)) {
|
||||||
|
Some(false) => {
|
||||||
|
let (area, corners) = self.region_corners(
|
||||||
|
&Coord2d {
|
||||||
|
x: x as i64,
|
||||||
|
y: y as i64,
|
||||||
|
},
|
||||||
|
&mut visited,
|
||||||
|
);
|
||||||
|
area * corners
|
||||||
|
}
|
||||||
|
Some(_) | None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc_generator(day12)]
|
||||||
|
fn parse(input: &[u8]) -> Farm {
|
||||||
|
input.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day12, part1)]
|
||||||
|
pub fn part1(farm: &Farm) -> u64 {
|
||||||
|
farm.regions_cost()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[aoc(day12, part2)]
|
||||||
|
pub fn part2(farm: &Farm) -> u64 {
|
||||||
|
farm.regions_discount_cost()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
const EXAMPLE: &[u8] = b"RRRRIICCFF
|
||||||
|
RRRRIICCCF
|
||||||
|
VVRRRCCFFF
|
||||||
|
VVRCCCJFFF
|
||||||
|
VVVVCJJCFE
|
||||||
|
VVIVCCJJEE
|
||||||
|
VVIIICJJEE
|
||||||
|
MIIIIIJJEE
|
||||||
|
MIIISIJEEE
|
||||||
|
MMMISSJEEE";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part1_example() {
|
||||||
|
assert_eq!(part1(&parse(EXAMPLE)), 1930);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part2_example() {
|
||||||
|
assert_eq!(part2(&parse(EXAMPLE)), 1206);
|
||||||
|
}
|
||||||
|
}
|
@ -11,5 +11,6 @@ pub mod day8;
|
|||||||
pub mod day9;
|
pub mod day9;
|
||||||
pub mod day10;
|
pub mod day10;
|
||||||
pub mod day11;
|
pub mod day11;
|
||||||
|
pub mod day12;
|
||||||
|
|
||||||
aoc_lib! { year = 2024 }
|
aoc_lib! { year = 2024 }
|
||||||
|
@ -2,9 +2,10 @@ use std::{
|
|||||||
fmt::{Debug, Display, Formatter, Write},
|
fmt::{Debug, Display, Formatter, Write},
|
||||||
io::{BufRead, Lines},
|
io::{BufRead, Lines},
|
||||||
iter::repeat,
|
iter::repeat,
|
||||||
|
ops::{Add, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Coord2d {
|
pub struct Coord2d {
|
||||||
pub x: i64,
|
pub x: i64,
|
||||||
pub y: i64,
|
pub y: i64,
|
||||||
@ -16,6 +17,26 @@ pub trait AsCoord2d {
|
|||||||
fn y(&self) -> i64;
|
fn y(&self) -> i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
||||||
|
type Output = Coord2d;
|
||||||
|
fn sub(self, rhs: T) -> Self::Output {
|
||||||
|
Coord2d {
|
||||||
|
x: self.x() - rhs.x(),
|
||||||
|
y: self.y() - rhs.y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsCoord2d> Add<T> for &Coord2d {
|
||||||
|
type Output = Coord2d;
|
||||||
|
fn add(self, rhs: T) -> Self::Output {
|
||||||
|
Coord2d {
|
||||||
|
x: self.x() + rhs.x(),
|
||||||
|
y: self.y() + rhs.y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsCoord2d for Coord2d {
|
impl AsCoord2d for Coord2d {
|
||||||
fn to_coord(self) -> Coord2d {
|
fn to_coord(self) -> Coord2d {
|
||||||
self
|
self
|
||||||
@ -40,6 +61,21 @@ impl AsCoord2d for (i64, i64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsCoord2d for (usize, usize) {
|
||||||
|
fn to_coord(self) -> Coord2d {
|
||||||
|
Coord2d {
|
||||||
|
x: self.0 as i64,
|
||||||
|
y: self.1 as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn x(&self) -> i64 {
|
||||||
|
self.0 as i64
|
||||||
|
}
|
||||||
|
fn y(&self) -> i64 {
|
||||||
|
self.1 as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Grid<T> {
|
pub struct Grid<T> {
|
||||||
pub data: Vec<T>,
|
pub data: Vec<T>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user