A input/day12/example => input/day12/example +10 -0
@@ 0,0 1,10 @@
+RRRRIICCFF
+RRRRIICCCF
+VVRRRCCFFF
+VVRCCCJFFF
+VVVVCJJCFE
+VVIVCCJJEE
+VVIIICJJEE
+MIIIIIJJEE
+MIIISIJEEE
+MMMISSJEEE
A input/day12/example1 => input/day12/example1 +5 -0
@@ 0,0 1,5 @@
+EEEEE
+EXXXX
+EEEEE
+EXXXX
+EEEEE
A input/day12/example2 => input/day12/example2 +6 -0
@@ 0,0 1,6 @@
+AAAAAA
+AAABBA
+AAABBA
+ABBAAA
+ABBAAA
+AAAAAA
A input/day12/examplesmall => input/day12/examplesmall +4 -0
@@ 0,0 1,4 @@
+AAAA
+BBCD
+BBCC
+EEEC
A src/day12/mod.rs => src/day12/mod.rs +11 -0
@@ 0,0 1,11 @@
+use std::path::Path;
+
+mod part1;
+mod part2;
+
+pub fn solve(input: &Path) -> anyhow::Result<()> {
+ println!("part one: {}", part1::part_one(input)?);
+ println!("part two: {}", part2::part_two(input)?);
+
+ Ok(())
+}
A src/day12/part1.rs => src/day12/part1.rs +105 -0
@@ 0,0 1,105 @@
+use std::{
+ collections::{BTreeSet, HashSet},
+ fs::File,
+ io::{BufRead, BufReader},
+ path::Path,
+};
+
+fn get_around(map: &[Vec<char>], p: (usize, usize)) -> Vec<(char, usize, usize)> {
+ let mut around_us = vec![];
+ if p.0 > 0 {
+ let (y, x) = (p.0 - 1, p.1);
+ around_us.push((map[y][x], y, x));
+ }
+ if let Some(row) = map.get(p.0 + 1) {
+ let (y, x) = (p.0 + 1, p.1);
+ around_us.push((row[x], y, x));
+ }
+ if p.1 > 0 {
+ let (y, x) = (p.0, p.1 - 1);
+ around_us.push((map[y][x], y, x));
+ }
+ if let Some(f) = map[p.0].get(p.1 + 1) {
+ let (y, x) = (p.0, p.1 + 1);
+ around_us.push((*f, y, x));
+ }
+ around_us
+}
+
+#[derive(Debug, Eq, Hash, PartialEq)]
+struct Region {
+ plots: BTreeSet<Plot>,
+}
+
+impl Region {
+ fn read_from_map(map: &[Vec<char>], c: &char, p: (usize, usize)) -> Self {
+ let mut region = Region {
+ plots: BTreeSet::new(),
+ };
+ region.plots.insert(Plot {
+ y: p.0,
+ x: p.1,
+ perimiter: (get_around(map, (p.0, p.1))
+ .iter()
+ .filter(|(f, _, _)| f == c)
+ .collect::<Vec<_>>()
+ .len() as i32
+ - 4)
+ .unsigned_abs() as usize,
+ });
+ region.read_recurse(map, c, p);
+ region
+ }
+
+ fn read_recurse(&mut self, map: &[Vec<char>], c: &char, p: (usize, usize)) {
+ for (f, y, x) in get_around(map, p) {
+ if f == *c {
+ let perimiter = (get_around(map, (y, x))
+ .iter()
+ .filter(|(f, _, _)| f == c)
+ .collect::<Vec<_>>()
+ .len() as i32
+ - 4)
+ .unsigned_abs() as usize;
+ if self.plots.insert(Plot { y, x, perimiter }) {
+ self.read_recurse(map, c, (y, x));
+ }
+ }
+ }
+ }
+
+ fn get_fence_cost(&self) -> usize {
+ self.plots
+ .iter()
+ .map(|p| -> usize { p.perimiter })
+ .sum::<usize>()
+ * self.plots.len()
+ }
+}
+
+#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+struct Plot {
+ y: usize,
+ x: usize,
+ perimiter: usize,
+}
+
+pub fn part_one(input: &Path) -> anyhow::Result<usize> {
+ let reader = BufReader::new(File::open(input)?);
+
+ let mut map: Vec<Vec<char>> = vec![];
+ for line in reader.lines() {
+ map.push(line?.chars().collect());
+ }
+ let mut regions: HashSet<Region> = HashSet::new();
+ for (y, row) in map.iter().enumerate() {
+ for (x, c) in row.iter().enumerate() {
+ regions.insert(Region::read_from_map(&map, c, (y, x)));
+ }
+ }
+
+ Ok(regions
+ .iter()
+ .map(|r| -> usize { r.get_fence_cost() })
+ .sum())
+}
A src/day12/part2.rs => src/day12/part2.rs +265 -0
@@ 0,0 1,265 @@
+use std::{
+ collections::{BTreeSet, HashSet},
+ fs::File,
+ io::{BufRead, BufReader},
+ path::Path,
+};
+
+fn get_around(map: &[Vec<char>], p: (usize, usize)) -> Vec<(char, usize, usize)> {
+ let mut around_us = vec![];
+ if p.0 > 0 {
+ let (y, x) = (p.0 - 1, p.1);
+ around_us.push((map[y][x], y, x));
+ }
+ if let Some(row) = map.get(p.0 + 1) {
+ let (y, x) = (p.0 + 1, p.1);
+ around_us.push((row[x], y, x));
+ }
+ if p.1 > 0 {
+ let (y, x) = (p.0, p.1 - 1);
+ around_us.push((map[y][x], y, x));
+ }
+ if let Some(f) = map[p.0].get(p.1 + 1) {
+ let (y, x) = (p.0, p.1 + 1);
+ around_us.push((*f, y, x));
+ }
+ around_us
+}
+
+#[derive(Debug, Eq, Hash, PartialEq)]
+struct Region {
+ c: char,
+ plots: BTreeSet<Plot>,
+}
+
+impl Region {
+ fn read_from_map(map: &[Vec<char>], c: &char, p: (usize, usize)) -> Self {
+ let mut region = Region {
+ c: *c,
+ plots: BTreeSet::new(),
+ };
+ region.plots.insert(Plot {
+ y: p.0,
+ x: p.1,
+ perimiter: (get_around(map, (p.0, p.1))
+ .iter()
+ .filter(|(f, _, _)| f == c)
+ .collect::<Vec<_>>()
+ .len() as i32
+ - 4)
+ .unsigned_abs() as usize,
+ });
+ region.read_recurse(map, c, p);
+ region
+ }
+
+ fn read_recurse(&mut self, map: &[Vec<char>], c: &char, p: (usize, usize)) {
+ for (f, y, x) in get_around(map, p) {
+ if f == *c {
+ let perimiter = (get_around(map, (y, x))
+ .iter()
+ .filter(|(f, _, _)| f == c)
+ .collect::<Vec<_>>()
+ .len() as i32
+ - 4)
+ .unsigned_abs() as usize;
+ if self.plots.insert(Plot { y, x, perimiter }) {
+ self.read_recurse(map, c, (y, x));
+ }
+ }
+ }
+ }
+
+ fn get_fence_cost(&self, map: &[Vec<char>], max_pos: usize) -> usize {
+ // Okay so, instead of perimiters * len, sides * len
+ // form all possible rows and columns of plots
+ // AAAA
+ // BB, B, B, BB
+ // B, B
+ // C, C, CC, C
+ // C, C
+ // D
+ // EEE
+
+ let mut rows_initial: Vec<Vec<Plot>> = vec![];
+ for i in 0..max_pos {
+ let mut a: Vec<Plot> = self.plots.iter().filter(|p| p.y == i).cloned().collect();
+ if !a.is_empty() {
+ a.sort_by_key(|p| p.x);
+ rows_initial.push(a);
+ }
+ }
+ let mut rows: Vec<Vec<Plot>> = vec![];
+ for row in &rows_initial {
+ let mut real_rows: Vec<Vec<Plot>> = vec![];
+ let mut last = None;
+ let mut current_real_row: Vec<Plot> = vec![];
+ for p in row {
+ if last.is_none() {
+ current_real_row.push(p.clone());
+ last = Some(p);
+ continue;
+ }
+
+ if p.x != last.unwrap().x + 1 {
+ real_rows.push(current_real_row.clone());
+ current_real_row.clear();
+ }
+ current_real_row.push(p.clone());
+ last = Some(p);
+ }
+ real_rows.push(current_real_row.clone());
+ rows.append(&mut real_rows);
+ }
+
+ let mut row_sides = 0;
+ for row in rows {
+ // top
+ let mut sides = 0;
+ let mut side_length = 0;
+ for p in &row {
+ let fence = if p.y > 0 {
+ map[p.y - 1][p.x] != self.c
+ } else {
+ true
+ };
+ if fence {
+ side_length += 1;
+ } else if side_length > 0 {
+ sides += 1;
+ side_length = 0;
+ }
+ }
+ if side_length > 0 {
+ sides += 1;
+ }
+ row_sides += sides;
+
+ // bottom
+ let mut sides = 0;
+ let mut side_length = 0;
+ for p in &row {
+ let fence = if let Some(below) = map.get(p.y + 1) {
+ below[p.x] != self.c
+ } else {
+ true
+ };
+ if fence {
+ side_length += 1;
+ } else if side_length > 0 {
+ sides += 1;
+ side_length = 0;
+ }
+ }
+ if side_length > 0 {
+ sides += 1;
+ }
+ row_sides += sides;
+ }
+
+ let mut cols_initial: Vec<Vec<Plot>> = vec![];
+ for i in 0..max_pos {
+ let mut a: Vec<Plot> = self.plots.iter().filter(|p| p.x == i).cloned().collect();
+ if !a.is_empty() {
+ a.sort_by_key(|p| p.y);
+ cols_initial.push(a);
+ }
+ }
+ let mut cols: Vec<Vec<Plot>> = vec![];
+ for col in &cols_initial {
+ let mut real_cols: Vec<Vec<Plot>> = vec![];
+ let mut last = None;
+ let mut current_real_col: Vec<Plot> = vec![];
+ for p in col {
+ if last.is_none() {
+ current_real_col.push(p.clone());
+ last = Some(p);
+ continue;
+ }
+
+ if p.y != last.unwrap().y + 1 {
+ real_cols.push(current_real_col.clone());
+ current_real_col.clear();
+ }
+ current_real_col.push(p.clone());
+ last = Some(p);
+ }
+ real_cols.push(current_real_col.clone());
+ cols.append(&mut real_cols);
+ }
+
+ let mut col_sides = 0;
+ for col in cols {
+ // right
+ let mut sides = 0;
+ let mut side_length = 0;
+ for p in &col {
+ let fence = if p.x > 0 {
+ map[p.y][p.x - 1] != self.c
+ } else {
+ true
+ };
+ if fence {
+ side_length += 1;
+ } else if side_length > 0 {
+ sides += 1;
+ side_length = 0;
+ }
+ }
+ if side_length > 0 {
+ sides += 1;
+ }
+ col_sides += sides;
+
+ // left
+ let mut sides = 0;
+ let mut side_length = 0;
+ for p in &col {
+ let fence = if let Some(left) = map[p.y].get(p.x + 1) {
+ *left != self.c
+ } else {
+ true
+ };
+ if fence {
+ side_length += 1;
+ } else if side_length > 0 {
+ sides += 1;
+ side_length = 0;
+ }
+ }
+ if side_length > 0 {
+ sides += 1;
+ }
+ col_sides += sides;
+ }
+
+ self.plots.len() * (row_sides + col_sides)
+ }
+}
+
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+struct Plot {
+ y: usize,
+ x: usize,
+ perimiter: usize,
+}
+
+pub fn part_two(input: &Path) -> anyhow::Result<usize> {
+ let reader = BufReader::new(File::open(input)?);
+
+ let mut map: Vec<Vec<char>> = vec![];
+ for line in reader.lines() {
+ map.push(line?.chars().collect());
+ }
+ let mut regions: HashSet<Region> = HashSet::new();
+ for (y, row) in map.iter().enumerate() {
+ for (x, c) in row.iter().enumerate() {
+ regions.insert(Region::read_from_map(&map, c, (y, x)));
+ }
+ }
+
+ Ok(regions
+ .iter()
+ .map(|r| -> usize { r.get_fence_cost(&map, map.len()) })
+ .sum())
+}
M src/main.rs => src/main.rs +8 -0
@@ 5,6 5,7 @@ use clap::{Parser, Subcommand};
mod day1;
mod day10;
mod day11;
+mod day12;
mod day2;
mod day3;
mod day4;
@@ 66,6 67,10 @@ enum DayArgs {
#[arg(short)]
input: PathBuf,
},
+ Day12 {
+ #[arg(short)]
+ input: PathBuf,
+ },
}
fn main() -> anyhow::Result<()> {
@@ 105,6 110,9 @@ fn main() -> anyhow::Result<()> {
DayArgs::Day11 { input } => {
day11::solve(&input)?;
}
+ DayArgs::Day12 { input } => {
+ day12::solve(&input)?;
+ }
}
Ok(())