use std::cmp::Ordering; use std::collections::HashMap; use crate::Day; use anyhow::Result; pub struct Day05(Vec); #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] struct Path { start: Coordinate, end: Coordinate, covers: Vec, } #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] struct Coordinate { x: i64, y: i64, } impl Day for Day05 { fn init(content: String) -> Result { let paths = content.lines().map(str_to_vent).collect::, _>>()?; Ok(Self(paths)) } fn part1(&self) -> Result { let new_vents = self.0.iter().filter(|p| p.is_straight()).cloned().collect::>(); Ok(format!("{}", covered_twice(&new_vents))) } fn part2(&self) -> Result { 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 { 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::,_>>()?; let end = b.split(',').map(str::parse).collect::,_>>()?; 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"); }