--- /dev/null
+use std::io::BufRead;
+
+use nalgebra::DMatrix;
+use rustc_hash::FxHashMap;
+
+type Garden = DMatrix<char>;
+
+pub fn read(reader: &mut dyn BufRead) -> Garden {
+ let mut data: Vec<char> = Vec::new();
+ let mut nb_rows = 0;
+ for l in reader.lines() {
+ nb_rows += 1;
+ data.append(&mut l.unwrap().chars().collect::<Vec<_>>());
+ }
+ Garden::from_row_slice(nb_rows, data.len() / nb_rows, &data)
+}
+
+#[derive(PartialEq, Eq, Hash)]
+enum FencePosition {
+ Up,
+ Down,
+ Left,
+ Right,
+}
+
+struct Fences {
+ horizontal: FxHashMap<(FencePosition, usize), Vec<(usize, usize)>>, // (j1, j2) indexed by i.
+ vertical: FxHashMap<(FencePosition, usize), Vec<(usize, usize)>>, // (i1, i2) indexed by j.
+ merge_fences: bool,
+}
+
+impl Fences {
+ pub fn new(merge_fences: bool) -> Self {
+ Self {
+ horizontal: FxHashMap::default(),
+ vertical: FxHashMap::default(),
+ merge_fences,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.horizontal.clear();
+ self.vertical.clear();
+ }
+
+ fn add_horizontal(&mut self, position: FencePosition, i: usize, j: usize) {
+ let fences = self.horizontal.entry((position, i)).or_default();
+ if self.merge_fences {
+ match (
+ fences.iter().position(|(_, j2)| *j2 == j),
+ fences.iter().position(|(j1, _)| *j1 == j + 1),
+ ) {
+ (Some(p_left), None) => fences[p_left].1 = j + 1,
+ (None, Some(p_right)) => fences[p_right].0 = j,
+ (Some(p_left), Some(p_right)) => {
+ fences[p_left].1 = fences[p_right].1;
+ fences.remove(p_right);
+ }
+ (None, None) => fences.push((j, j + 1)),
+ }
+ } else {
+ fences.push((j, j + 1));
+ }
+ }
+
+ fn add_vertical(&mut self, position: FencePosition, i: usize, j: usize) {
+ let fences = self.vertical.entry((position, j)).or_default();
+ if self.merge_fences {
+ match (
+ fences.iter().position(|(_, i2)| *i2 == i),
+ fences.iter().position(|(i1, _)| *i1 == i + 1),
+ ) {
+ (Some(p_above), None) => fences[p_above].1 = i + 1,
+ (None, Some(p_below)) => fences[p_below].0 = i,
+ (Some(p_above), Some(p_below)) => {
+ fences[p_above].1 = fences[p_below].1;
+ fences.remove(p_below);
+ }
+ (None, None) => fences.push((i, i + 1)),
+ }
+ } else {
+ fences.push((i, i + 1))
+ }
+ }
+
+ pub fn add(&mut self, position: FencePosition, i: usize, j: usize) {
+ match position {
+ FencePosition::Up => self.add_horizontal(position, i, j),
+ FencePosition::Down => self.add_horizontal(position, i + 1, j),
+ FencePosition::Left => self.add_vertical(position, i, j),
+ FencePosition::Right => self.add_vertical(position, i, j + 1),
+ }
+ }
+
+ pub fn count(&self) -> usize {
+ self.horizontal.values().fold(0, |acc, f| acc + f.len())
+ + self.vertical.values().fold(0, |acc, f| acc + f.len())
+ }
+}
+
+pub fn total_price(garden: &Garden, merge_fences: bool) -> usize {
+ let nrows = garden.nrows();
+ let ncols = garden.ncols();
+ let mut visited = DMatrix::<bool>::repeat(nrows, ncols, false);
+ let mut to_visit = Vec::new();
+ let mut fences = Fences::new(merge_fences);
+
+ let mut price = 0;
+ for j in 0..ncols {
+ for i in 0..nrows {
+ let region = garden[(i, j)];
+ fences.clear();
+ to_visit.clear();
+ to_visit.push((i, j));
+ let mut area = 0;
+ while let Some((i, j)) = to_visit.pop() {
+ if visited[(i, j)] {
+ continue;
+ }
+ visited[(i, j)] = true;
+ area += 1;
+
+ for (position, i2, j2) in [
+ (FencePosition::Up, i.wrapping_sub(1), j),
+ (FencePosition::Down, i + 1, j),
+ (FencePosition::Left, i, j.wrapping_sub(1)),
+ (FencePosition::Right, i, j + 1),
+ ] {
+ if i2 >= nrows || j2 >= ncols || garden[(i2, j2)] != region {
+ fences.add(position, i, j);
+ } else {
+ to_visit.push((i2, j2));
+ }
+ }
+ }
+
+ price += area * fences.count();
+ }
+ }
+ price
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static GARDEN_1: &str = "AAAA
+BBCD
+BBCC
+EEEC";
+
+ static GARDEN_2: &str = "OOOOO
+OXOXO
+OOOOO
+OXOXO
+OOOOO";
+
+ static GARDEN_3: &str = "EEEEE
+EXXXX
+EEEEE
+EXXXX
+EEEEE";
+
+ static GARDEN_4: &str = "AAAAAA
+AAABBA
+AAABBA
+ABBAAA
+ABBAAA
+AAAAAA";
+
+ static GARDEN_5: &str = "RRRRIICCFF
+RRRRIICCCF
+VVRRRCCFFF
+VVRCCCJFFF
+VVVVCJJCFE
+VVIVCCJJEE
+VVIIICJJEE
+MIIIIIJJEE
+MIIISIJEEE
+MMMISSJEEE";
+
+ #[test]
+ fn part1() {
+ let garden1 = read(&mut GARDEN_1.as_bytes());
+ assert_eq!(total_price(&garden1, false), 140);
+
+ let garden2 = read(&mut GARDEN_2.as_bytes());
+ assert_eq!(total_price(&garden2, false), 772);
+
+ let garden3 = read(&mut GARDEN_3.as_bytes());
+ assert_eq!(total_price(&garden3, false), 692);
+
+ let garden4 = read(&mut GARDEN_4.as_bytes());
+ assert_eq!(total_price(&garden4, false), 1184);
+
+ let garden5 = read(&mut GARDEN_5.as_bytes());
+ assert_eq!(total_price(&garden5, false), 1930);
+ }
+
+ #[test]
+ fn part2() {
+ let garden1 = read(&mut GARDEN_1.as_bytes());
+ assert_eq!(total_price(&garden1, true), 80);
+
+ let garden2 = read(&mut GARDEN_2.as_bytes());
+ assert_eq!(total_price(&garden2, true), 436);
+
+ let garden3 = read(&mut GARDEN_3.as_bytes());
+ assert_eq!(total_price(&garden3, true), 236);
+
+ let garden4 = read(&mut GARDEN_4.as_bytes());
+ assert_eq!(total_price(&garden4, true), 368);
+
+ let garden5 = read(&mut GARDEN_5.as_bytes());
+ assert_eq!(total_price(&garden5, true), 1206);
+ }
+
+ static GARDEN_6: &str = "XXXXXX
+XOOOOX
+XOXXXX
+XXXEEE";
+
+ #[test]
+ fn part2_b() {
+ let garden6 = read(&mut GARDEN_6.as_bytes());
+ assert_eq!(total_price(&garden6, true), 234);
+ }
+}