From: Jack Kinsey Date: Fri, 13 Dec 2024 05:38:45 +0000 (-0500) Subject: Complete day 12 part 1 X-Git-Url: http://git.jkinsey.net/?a=commitdiff_plain;h=b7b93bdba7d2fe6ad5a12f1c5320756f60a3b93f;p=adventofcode2024.git Complete day 12 part 1 --- diff --git a/src/day12.rs b/src/day12.rs new file mode 100644 index 0000000..a8fd499 --- /dev/null +++ b/src/day12.rs @@ -0,0 +1,254 @@ +use std::collections::HashSet; +use std::ops::{Add, Index, IndexMut, Sub}; + +type Plot = char; + +#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)] +struct Point(i32, i32); + +#[derive(Debug, Clone)] +struct Region { + plots: HashSet, + plant: char, +} + +impl Region { + fn perimeter(&self) -> u64 { + self.plots + .iter() + .map(|p| { + 4 - p + .adjacent(i32::MAX.try_into().unwrap()) + .filter(|q| self.plots.contains(q)) + .count() + }) + .sum::() + .try_into() + .unwrap() + } + + fn area(&self) -> u64 { + self.plots.len().try_into().unwrap() + } + + fn price(&self) -> u64 { + self.area() * self.perimeter() + } +} + +#[rustfmt::skip] +const DIRS: [Point; 4] = [ + Point(-1, 0), + Point(0, -1), /* ctr */ Point(0, 1), + Point( 1, 0), +]; + +impl Point { + fn in_bounds(&self, bound: usize) -> bool { + let bound: i32 = bound.try_into().unwrap(); + self.0 >= 0 && self.1 >= 0 && self.0 < bound && self.1 < bound + } + + fn adjacent(self, bound: usize) -> impl Iterator { + DIRS.iter() + .copied() + .map(move |d| d + self) + .filter(move |p| p.in_bounds(bound)) + } +} + +impl Add for Point { + type Output = Point; + + fn add(self, rhs: Self) -> Self::Output { + Point(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Sub for Point { + type Output = Point; + + fn sub(self, rhs: Self) -> Self::Output { + Point(self.0 - rhs.0, self.1 - rhs.1) + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +struct Map(Vec>); + +impl Index for Map { + type Output = T; + + fn index(&self, index: Point) -> &Self::Output { + let i: usize = index.0.try_into().unwrap(); + let j: usize = index.1.try_into().unwrap(); + &self.0[i][j] + } +} + +impl IndexMut for Map { + fn index_mut(&mut self, index: Point) -> &mut Self::Output { + let i: usize = index.0.try_into().unwrap(); + let j: usize = index.1.try_into().unwrap(); + &mut self.0[i][j] + } +} + +impl Map { + fn points(&self) -> impl Iterator + use<'_, T> { + self.0 + .iter() + .zip(0..) + .flat_map(move |(v, i)| v.iter().zip(0..).map(move |(_, j)| Point(i, j))) + } +} + +impl Map { + fn regions(&self) -> MapRegions { + MapRegions::new(self) + } +} + +#[derive()] +struct MapRegions<'a> { + map: &'a Map, + seen: Map, + iter: Vec, + bound: usize, +} + +impl<'a> MapRegions<'a> { + fn new(map: &'a Map) -> Self { + MapRegions { + map, + seen: Map(std::iter::repeat_with(|| vec![false; map.0.len()]) + .take(map.0.len()) + .collect()), + iter: map.points().collect(), + bound: map.0.len(), + } + } +} + +impl Iterator for MapRegions<'_> { + type Item = Region; + + fn next(&mut self) -> Option { + let mut p = self.iter.pop()?; + while self.seen[p] { + p = self.iter.pop()?; + } + let c = self.map[p]; + let mut iterators = vec![]; + let mut points = vec![p]; + let mut plots = HashSet::new(); + while !points.is_empty() { + iterators.extend(points.drain(..).filter_map(|p| { + if !self.seen[p] && self.map[p] == c { + self.seen[p] = true; + plots.insert(p); + Some(p.adjacent(self.bound)) + } else { + None + } + })); + points.extend(iterators.drain(..).flatten()); + } + Some(Region { plots, plant: c }) + } +} + +fn parse(input: &str) -> Map { + Map(input.lines().map(|l| l.chars().collect()).collect()) +} + +pub fn part1(input: &str) -> u64 { + parse(input).regions().map(|r| r.price()).sum::() +} + +pub fn part2(input: &str) -> u64 { + 0 +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT_STR: &str = concat!( + "RRRRIICCFF\n", + "RRRRIICCCF\n", + "VVRRRCCFFF\n", + "VVRCCCJFFF\n", + "VVVVCJJCFE\n", + "VVIVCCJJEE\n", + "VVIIICJJEE\n", + "MIIIIIJJEE\n", + "MIIISIJEEE\n", + "MMMISSJEEE\n", + ); + const TINY_INPUT: &str = concat!("AAAA\n", "BBCD\n", "BBCC\n", "EEEC\n",); + + #[test] + fn test_parse() { + assert_eq!( + parse(INPUT_STR), + Map(vec![ + vec!['R', 'R', 'R', 'R', 'I', 'I', 'C', 'C', 'F', 'F'], + vec!['R', 'R', 'R', 'R', 'I', 'I', 'C', 'C', 'C', 'F'], + vec!['V', 'V', 'R', 'R', 'R', 'C', 'C', 'F', 'F', 'F'], + vec!['V', 'V', 'R', 'C', 'C', 'C', 'J', 'F', 'F', 'F'], + vec!['V', 'V', 'V', 'V', 'C', 'J', 'J', 'C', 'F', 'E'], + vec!['V', 'V', 'I', 'V', 'C', 'C', 'J', 'J', 'E', 'E'], + vec!['V', 'V', 'I', 'I', 'I', 'C', 'J', 'J', 'E', 'E'], + vec!['M', 'I', 'I', 'I', 'I', 'I', 'J', 'J', 'E', 'E'], + vec!['M', 'I', 'I', 'I', 'S', 'I', 'J', 'E', 'E', 'E'], + vec!['M', 'M', 'M', 'I', 'S', 'S', 'J', 'E', 'E', 'E'] + ]) + ) + } + + #[rustfmt::skip] + #[test] + fn test_points() { + assert_eq!( + parse(TINY_INPUT).points().collect::>(), + [ + Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3), + Point(1, 0), Point(1, 1), Point(1, 2), Point(1, 3), + Point(2, 0), Point(2, 1), Point(2, 2), Point(2, 3), + Point(3, 0), Point(3, 1), Point(3, 2), Point(3, 3) + ], + ) + } + + #[test] + fn test_regions() { + assert_eq!( + parse(TINY_INPUT) + .regions() + .map(|Region { plots: _, plant: c }| c) + .collect::>(), + ['C', 'E', 'B', 'D', 'A'], + ) + } + + #[test] + fn test_price() { + assert_eq!( + parse(INPUT_STR).regions().map(|r| r.price()).sum::(), + 1930 + ) + } + + #[test] + #[ignore] + fn test_part1() { + assert_eq!(part1(&crate::input(0).unwrap()), 0) + } + + #[test] + #[ignore] + fn test_part2() { + assert_eq!(part2(&crate::input(0).unwrap()), 0) + } +} diff --git a/src/main.rs b/src/main.rs index 80cc634..54a3fc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ pub mod day08; pub mod day09; pub mod day10; pub mod day11; -// pub mod day12; +pub mod day12; // pub mod day13; // pub mod day14; // pub mod day15; @@ -38,7 +38,7 @@ const DAYS: &[(Part, Part)] = &[ (day09::part1, day09::part2), (day10::part1, day10::part2), (day11::part1, day11::part2), - // (day12::part1, day12::part2), + (day12::part1, day12::part2), // (day13::part1, day13::part2), // (day14::part1, day14::part2), // (day15::part1, day15::part2),