AOC2021's first actual refactor.
This commit is contained in:
230
src/bin/day9.rs
230
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<T> {
|
||||
data: Vec<T>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct Value(u8);
|
||||
|
||||
@@ -46,163 +30,33 @@ impl TryFrom<char> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + TryFrom<char, Error = Oopsie>> Graph<T> {
|
||||
fn from_file_data(file_data: &str) -> Result<Graph<T>, 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<Value>, x: usize, y: usize) -> Vec<Point<Value>> {
|
||||
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<Point<T>> {
|
||||
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<Point<T>> {
|
||||
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<Value> {
|
||||
fn basin_around(&self, x: usize, y: usize) -> Vec<Point<Value>> {
|
||||
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<T>,
|
||||
curx: usize,
|
||||
cury: usize,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for Points<'a, T>
|
||||
where
|
||||
T: Clone + TryFrom<char, Error = Oopsie>,
|
||||
{
|
||||
type Item = Point<'a, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<Value>,
|
||||
underlying: &'a mut Points<'a, Value>,
|
||||
}
|
||||
|
||||
@@ -212,7 +66,7 @@ impl<'a> Iterator for LowPoints<'a> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<Value>, points: &'a mut Points<'a, Value>) -> LowPoints<'a> {
|
||||
LowPoints {
|
||||
graph,
|
||||
underlying: points,
|
||||
}
|
||||
}
|
||||
|
||||
fn low_points(graph: &Graph<Value>) -> usize {
|
||||
graph
|
||||
.points()
|
||||
.low_points()
|
||||
.map(|x| x.value.0 as usize + 1)
|
||||
.sum()
|
||||
fn score_low_points(graph: &mut Graph<Value>) -> 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<Value>) -> 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<Value> = 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<Value> = 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<Value> = Graph::from_file_data(TEST_DATA)?;
|
||||
println!("Test low point sum: {}", low_points(&test_graph));
|
||||
let mut test_graph: Graph<Value> = 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<Value> = Graph::from_file_data(DAY9_DATA)?;
|
||||
println!("Real graph sum: {}", low_points(&real_graph));
|
||||
let mut real_graph: Graph<Value> = 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(())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod map;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn from_file_data<T: FromStr>(filedata: &str) -> Result<Vec<T>, T::Err> {
|
||||
|
||||
147
src/map.rs
Normal file
147
src/map.rs
Normal file
@@ -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<T> {
|
||||
data: Vec<T>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone + TryFrom<char, Error = Oopsie>> Graph<T> {
|
||||
pub fn from_file_data(file_data: &str) -> Result<Graph<T>, 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<Point<T>> {
|
||||
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<Point<T>> {
|
||||
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<T>,
|
||||
curx: usize,
|
||||
cury: usize,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for Points<'a, T>
|
||||
where
|
||||
T: Clone + TryFrom<char, Error = Oopsie>,
|
||||
{
|
||||
type Item = Point<'a, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user