From cce5476b73ffebcbccae8c569fc315234526c920 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Sat, 11 Dec 2021 08:34:06 -0800 Subject: [PATCH] AOC2021's first actual refactor. --- src/bin/day9.rs | 230 ++++++++++-------------------------------------- src/lib.rs | 2 + src/map.rs | 147 +++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 183 deletions(-) create mode 100644 src/map.rs diff --git a/src/bin/day9.rs b/src/bin/day9.rs index 6500ee4..661fdd6 100644 --- a/src/bin/day9.rs +++ b/src/bin/day9.rs @@ -1,26 +1,10 @@ +use advent2021::map::{Graph, Oopsie, Point, Points}; use itertools::Itertools; use std::fmt; -use thiserror::Error; const TEST_DATA: &str = include_str!("../../data/day9t.txt"); const DAY9_DATA: &str = include_str!("../../data/day9a.txt"); -#[derive(Debug, Error)] -enum Oopsie { - #[error("Tried to parse an empty graph?")] - EmptyGraph, - #[error("Got a weird, inconsistent line width around {0}")] - InconsistentGraphWidth(usize), - #[error("Got weird character parsing graph: {0}")] - BadCharacter(char), -} - -struct Graph { - data: Vec, - width: usize, - height: usize, -} - #[derive(Clone, PartialEq)] struct Value(u8); @@ -46,163 +30,33 @@ impl TryFrom for Value { } } -impl> Graph { - fn from_file_data(file_data: &str) -> Result, Oopsie> { - let mut data = Vec::with_capacity(file_data.len()); - let mut width = None; - let mut height = 0; - let mut temp_width = 0; +fn basin_around(graph: &Graph, x: usize, y: usize) -> Vec> { + let base = match graph.get(x, y) { + None => return Vec::new(), + Some(p) => p, + }; + let mut retval = Vec::new(); + let mut queue = vec![base.clone()]; - for c in file_data.chars() { - if c == '\n' { - height += 1; - - if let Some(x) = width { - if x != temp_width { - return Err(Oopsie::InconsistentGraphWidth(height)); - } - } else { - width = Some(temp_width); - } - - temp_width = 0; - } else { - data.push(T::try_from(c)?); - temp_width += 1; - } - } - - if temp_width != 0 { - height += 1; - } - - if height == 0 { - return Err(Oopsie::EmptyGraph); - } - - Ok(Graph { - data, - width: width.unwrap(), - height, - }) - } - - fn get(&self, x: usize, y: usize) -> Option> { - if x >= self.width || y >= self.height { - return None; - } - - Some(Point { - x, - y, - value: &self.data[(y * self.width) + x], - }) - } - - fn points(&self) -> Points<'_, T> { - Points { - graph: self, - curx: 0, - cury: 0, - } - } - - fn neighbors(&self, x: usize, y: usize) -> Vec> { - let mut retval = Vec::new(); - - if x > 0 { - retval.push(self.get(x - 1, y).unwrap()); - } - if y > 0 { - retval.push(self.get(x, y - 1).unwrap()); - } - if let Some(v) = self.get(x + 1, y) { - retval.push(v); - } - if let Some(v) = self.get(x, y + 1) { - retval.push(v); - } - - retval - } -} - -impl Graph { - fn basin_around(&self, x: usize, y: usize) -> Vec> { - let base = match self.get(x, y) { - None => return Vec::new(), - Some(p) => p, - }; - let mut retval = Vec::new(); - let mut queue = vec![base.clone()]; - - while let Some(p) = queue.pop() { - if *p.value != 9 { - for x in self.neighbors(p.x, p.y).drain(..) { - if !retval.contains(&x) && !queue.contains(&x) { - queue.push(x); - } - } - - if !retval.contains(&p) { - retval.push(p); + while let Some(p) = queue.pop() { + if *p.value != 9 { + for x in graph.neighbors(p.x, p.y).drain(..) { + if !retval.contains(&x) && !queue.contains(&x) { + queue.push(x); } } + + if !retval.contains(&p) { + retval.push(p); + } } - - retval } -} -#[derive(Clone)] -struct Point<'a, T> { - x: usize, - y: usize, - value: &'a T, -} - -impl<'a, T> fmt::Debug for Point<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({},{})", self.x, self.y) - } -} - -impl<'a, T> PartialEq for Point<'a, T> { - fn eq(&self, other: &Self) -> bool { - self.x == other.x && self.y == other.y - } -} - -struct Points<'a, T> { - graph: &'a Graph, - curx: usize, - cury: usize, -} - -impl<'a, T> Iterator for Points<'a, T> -where - T: Clone + TryFrom, -{ - type Item = Point<'a, T>; - - fn next(&mut self) -> Option { - if self.cury == self.graph.height { - return None; - } - - let next_value = self.graph.get(self.curx, self.cury)?; - - self.curx += 1; - if self.curx == self.graph.width { - self.curx = 0; - self.cury += 1; - } - - Some(next_value) - } + retval } struct LowPoints<'a> { + graph: &'a Graph, underlying: &'a mut Points<'a, Value>, } @@ -212,7 +66,7 @@ impl<'a> Iterator for LowPoints<'a> { fn next(&mut self) -> Option { loop { let candidate = self.underlying.next()?; - let neighbors = self.underlying.graph.neighbors(candidate.x, candidate.y); + let neighbors = self.graph.neighbors(candidate.x, candidate.y); if neighbors.iter().all(|x| (*x).value.0 > candidate.value.0) { return Some(candidate); @@ -221,38 +75,48 @@ impl<'a> Iterator for LowPoints<'a> { } } -impl<'a> Points<'a, Value> { - fn low_points(&'a mut self) -> LowPoints<'a> { - LowPoints { underlying: self } +fn low_points<'a>(graph: &'a Graph, points: &'a mut Points<'a, Value>) -> LowPoints<'a> { + LowPoints { + graph, + underlying: points, } } -fn low_points(graph: &Graph) -> usize { - graph - .points() - .low_points() - .map(|x| x.value.0 as usize + 1) - .sum() +fn score_low_points(graph: &mut Graph) -> usize { + let mut points = graph.points(); + let low_points = low_points(graph, &mut points); + low_points.map(|x| x.value.0 as usize + 1).sum() } fn basins(graph: &Graph) -> usize { - graph - .points() - .low_points() - .map(|p| graph.basin_around(p.x, p.y)) + let mut points = graph.points(); + let low_points = low_points(graph, &mut points); + low_points + .map(|p| basin_around(graph, p.x, p.y)) .sorted_by(|a, b| a.len().cmp(&b.len()).reverse()) .take(3) .map(|x| x.len()) .product() } +#[test] +fn regression() { + let mut test_graph: Graph = Graph::from_file_data(TEST_DATA).unwrap(); + assert_eq!(15, score_low_points(&mut test_graph)); + assert_eq!(1134, basins(&test_graph)); + + let mut real_graph: Graph = Graph::from_file_data(DAY9_DATA).unwrap(); + assert_eq!(462, score_low_points(&mut real_graph)); + assert_eq!(1397760, basins(&real_graph)); +} + fn day9() -> Result<(), Oopsie> { - let test_graph: Graph = Graph::from_file_data(TEST_DATA)?; - println!("Test low point sum: {}", low_points(&test_graph)); + let mut test_graph: Graph = Graph::from_file_data(TEST_DATA)?; + println!("Test low point sum: {}", score_low_points(&mut test_graph)); println!("Test basin sum is: {}", basins(&test_graph)); - let real_graph: Graph = Graph::from_file_data(DAY9_DATA)?; - println!("Real graph sum: {}", low_points(&real_graph)); + let mut real_graph: Graph = Graph::from_file_data(DAY9_DATA)?; + println!("Real graph sum: {}", score_low_points(&mut real_graph)); println!("Real basin sum is: {}", basins(&real_graph)); Ok(()) diff --git a/src/lib.rs b/src/lib.rs index a795e5c..b0e6977 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +pub mod map; + use std::str::FromStr; pub fn from_file_data(filedata: &str) -> Result, T::Err> { diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..527bbe9 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,147 @@ +use core::fmt; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Oopsie { + #[error("Tried to parse an empty graph?")] + EmptyGraph, + #[error("Got a weird, inconsistent line width around {0}")] + InconsistentGraphWidth(usize), + #[error("Got weird character parsing graph: {0}")] + BadCharacter(char), +} + +pub struct Graph { + data: Vec, + width: usize, + height: usize, +} + +impl> Graph { + pub fn from_file_data(file_data: &str) -> Result, Oopsie> { + let mut data = Vec::with_capacity(file_data.len()); + let mut width = None; + let mut height = 0; + let mut temp_width = 0; + + for c in file_data.chars() { + if c == '\n' { + height += 1; + + if let Some(x) = width { + if x != temp_width { + return Err(Oopsie::InconsistentGraphWidth(height)); + } + } else { + width = Some(temp_width); + } + + temp_width = 0; + } else { + data.push(T::try_from(c)?); + temp_width += 1; + } + } + + if temp_width != 0 { + height += 1; + } + + if height == 0 { + return Err(Oopsie::EmptyGraph); + } + + Ok(Graph { + data, + width: width.unwrap(), + height, + }) + } + + pub fn get(&self, x: usize, y: usize) -> Option> { + if x >= self.width || y >= self.height { + return None; + } + + Some(Point { + x, + y, + value: &self.data[(y * self.width) + x], + }) + } + + pub fn points(&self) -> Points<'_, T> { + Points { + graph: self, + curx: 0, + cury: 0, + } + } + + pub fn neighbors(&self, x: usize, y: usize) -> Vec> { + let mut retval = Vec::new(); + + if x > 0 { + retval.push(self.get(x - 1, y).unwrap()); + } + if y > 0 { + retval.push(self.get(x, y - 1).unwrap()); + } + if let Some(v) = self.get(x + 1, y) { + retval.push(v); + } + if let Some(v) = self.get(x, y + 1) { + retval.push(v); + } + + retval + } +} + +#[derive(Clone)] +pub struct Point<'a, T> { + pub x: usize, + pub y: usize, + pub value: &'a T, +} + +impl<'a, T> fmt::Debug for Point<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({},{})", self.x, self.y) + } +} + +impl<'a, T> PartialEq for Point<'a, T> { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +pub struct Points<'a, T> { + graph: &'a Graph, + curx: usize, + cury: usize, +} + +impl<'a, T> Iterator for Points<'a, T> +where + T: Clone + TryFrom, +{ + type Item = Point<'a, T>; + + fn next(&mut self) -> Option { + if self.cury == self.graph.height { + return None; + } + + let next_value = self.graph.get(self.curx, self.cury)?; + + self.curx += 1; + if self.curx == self.graph.width { + self.curx = 0; + self.cury += 1; + } + + Some(next_value) + } +}