AoC2021/src/day05.rs

187 lines
4.8 KiB
Rust

use std::cmp::Ordering;
use std::collections::HashMap;
use crate::Day;
use anyhow::Result;
pub struct Day05(Vec<Path>);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
struct Path {
start: Coordinate,
end: Coordinate,
covers: Vec<Coordinate>,
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
struct Coordinate {
x: i64,
y: i64,
}
impl Day for Day05 {
fn init(content: String) -> Result<Self> {
let paths = content.lines().map(str_to_vent).collect::<Result<Vec<Path>, _>>()?;
Ok(Self(paths))
}
fn part1(&self) -> Result<String> {
let new_vents = self.0.iter().filter(|p| p.is_straight()).cloned().collect::<Vec<Path>>();
Ok(format!("{}", covered_twice(&new_vents)))
}
fn part2(&self) -> Result<String> {
Ok(format!("{}", covered_twice(&self.0)))
}
}
fn covered_twice(vents: &[Path]) -> usize {
let mut m = HashMap::new();
for vent in vents {
for cover in &vent.covers {
let m = m.entry(cover).or_insert(0);
*m += 1;
}
}
m.values().copied().filter(|x| *x >= 2).count()
}
impl Path {
fn is_straight(&self) -> bool {
self.start.x == self.end.x || self.start.y == self.end.y
}
}
impl Coordinate {
fn dir_to(&self, other: &Coordinate) -> Direction {
return if self.x < other.x {
match self.y.cmp(&other.y) {
Ordering::Less => Direction::SE,
Ordering::Equal => Direction::E,
Ordering::Greater => Direction::NE,
}
} else if self.x == other.x {
match self.y.cmp(&other.y) {
Ordering::Less => Direction::S,
Ordering::Equal => panic!("why are the start and end equal??"),
Ordering::Greater => Direction::N,
}
} else if self.y < other.y {
Direction::SW
} else if self.y == other.y {
Direction::W
} else {
Direction::NW
};
}
#[cfg(test)]
fn new(x: i64, y: i64) -> Self {
Coordinate { x, y }
}
fn from_vec(v: &[i64]) -> Self {
Coordinate { x: v[0], y: v[1] }
}
}
#[derive(Eq, PartialEq, Debug)]
enum Direction {
NE,
NW,
SE,
SW,
N,
E,
W,
S,
}
impl Direction {
fn coord_diff(&self) -> (i64, i64) {
match self {
Direction::NE => (1, -1),
Direction::NW => (-1, -1),
Direction::SE => (1, 1),
Direction::SW => (-1, 1),
Direction::N => (0, -1),
Direction::E => (1, 0),
Direction::W => (-1, 0),
Direction::S => (0, 1),
}
}
}
fn str_to_vent(st: &str) -> Result<Path> {
let mut v = st.split(" -> ");
let a = v.next().ok_or_else(|| anyhow::Error::msg("could not get next..."))?;
let b = v.next().ok_or_else(|| anyhow::Error::msg("could not get next..."))?;
let start = a.split(',').map(str::parse).collect::<Result<Vec<i64>,_>>()?;
let end = b.split(',').map(str::parse).collect::<Result<Vec<i64>,_>>()?;
let start = Coordinate::from_vec(&start);
let end = Coordinate::from_vec(&end);
let dir = start.dir_to(&end).coord_diff();
let mut covers = vec![];
let mut c = start.clone();
while c != end {
covers.push(c.clone());
c.x += dir.0;
c.y += dir.1;
}
covers.push(end.clone());
Ok(Path {
start,
end,
covers,
})
}
#[cfg(test)]
mod tests {
use crate::{Day05, day_tests};
use crate::day05::{Coordinate, Direction};
use crate::day::Day;
use anyhow::Result;
const INPUT: &str = r"0,9 -> 5,9
8,0 -> 0,8
9,4 -> 3,4
2,2 -> 2,1
7,0 -> 7,4
6,4 -> 2,0
0,9 -> 2,9
3,4 -> 1,4
0,0 -> 8,8
5,5 -> 8,2";
#[test]
fn directions() {
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(0, 0)), Direction::NW);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(9, 9)), Direction::SE);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(9, 0)), Direction::NE);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(0, 9)), Direction::SW);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(0, 5)), Direction::W);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(5, 0)), Direction::N);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(9, 5)), Direction::E);
assert_eq!(Coordinate::new(5, 5).dir_to(&Coordinate::new(5, 9)), Direction::S);
}
#[test]
fn part1_test() -> Result<()> {
let d = Day05::init(INPUT.to_string())?;
assert_eq!("5", d.part1()?);
Ok(())
}
#[test]
fn part2_test() -> Result<()> {
let d = Day05::init(INPUT.to_string())?;
assert_eq!("12", d.part2()?);
Ok(())
}
day_tests!(Day05, "05", "6564", "19172");
}