diff --git a/src/days/day13.rs b/src/days/day13.rs index 209aff3..ff8c5af 100644 --- a/src/days/day13.rs +++ b/src/days/day13.rs @@ -3,6 +3,8 @@ use std::{cmp::Ordering, fmt::Debug}; use aoc_runner_derive::{aoc, aoc_generator}; use itertools::{EitherOrBoth, Itertools}; +use crate::util::parse_num; + #[derive(Clone, PartialEq, Eq)] enum Data { Integer(u32), @@ -68,13 +70,8 @@ impl Data { let rest2 = rest2.strip_prefix(',')?; rest = rest2; } - } else if s.chars().next()?.is_alphanumeric() { - let (num_part, rest) = s - .char_indices() - .find_map(|(i, c)| (!c.is_numeric()).then_some(s.split_at(i))) - .unwrap_or((s, "")); - - Some((Self::Integer(num_part.parse().ok()?), rest)) + } else if let Some((num, rest)) = parse_num(s) { + Some((Self::Integer(num), rest)) } else { return None; } diff --git a/src/days/day14.rs b/src/days/day14.rs new file mode 100644 index 0000000..d82d2cb --- /dev/null +++ b/src/days/day14.rs @@ -0,0 +1,158 @@ +use std::{collections::HashSet, ops::Add}; + +use aoc_runner_derive::{aoc, aoc_generator}; +use itertools::Itertools; + +use crate::util::parse_num; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct Coord { + x: i32, + y: i32, +} + +impl Coord { + fn parse(s: &str) -> Option<(Self, &str)> { + let (x, s) = parse_num(s)?; + let s = s.strip_prefix(',')?; + let (y, s) = parse_num(s)?; + Some((Self { x, y }, s)) + } +} + +impl Add<(i32, i32)> for Coord { + type Output = Self; + + fn add(self, rhs: (i32, i32)) -> Self::Output { + Self { + x: self.x + rhs.0, + y: self.y + rhs.1, + } + } +} + +struct Path(Vec); + +impl Path { + fn parse(s: &str) -> Option<(Self, &str)> { + let (first, mut s) = Coord::parse(s)?; + let mut vec = Vec::from([first]); + + loop { + let Some(s2) = s.strip_prefix(" -> ") else { + return Some((Self(vec), s)) + }; + let (coord, s2) = Coord::parse(s2)?; + + if coord.x != vec.last().unwrap().x && coord.y != vec.last().unwrap().y { + return None; + } + + vec.push(coord); + s = s2; + } + } + + fn intersects(&self, coord: Coord) -> bool { + for (a, b) in self.0.iter().tuple_windows() { + if a.y == b.y { + if coord.y == a.y + && ((a.x <= coord.x && coord.x <= b.x) || (b.x <= coord.x && coord.x <= a.x)) + { + return true; + } + } else if a.x == b.x { + if coord.x == a.x + && ((a.y <= coord.y && coord.y <= b.y) || (b.y <= coord.y && coord.y <= a.y)) + { + return true; + } + } else { + unreachable!() + } + } + + false + } +} + +#[aoc_generator(day14)] +fn generator(input: &str) -> Vec { + input.lines().map(|l| Path::parse(l).unwrap().0).collect() +} + +fn is_blocked(rocks: &[Path], sand: &HashSet, coord: Coord) -> bool { + sand.contains(&coord) || rocks.iter().any(|path| path.intersects(coord)) +} + +#[aoc(day14, part1)] +fn part1(input: &[Path]) -> usize { + let abyss_depth = input + .iter() + .flat_map(|path| path.0.iter().map(|coord| coord.y)) + .max() + .unwrap() + + 1; // for good measure + + let mut sand = HashSet::new(); + + loop { + let mut particle = Coord { x: 500, y: 0 }; + + loop { + if !is_blocked(input, &sand, particle + (0, 1)) { + particle = particle + (0, 1); + } else if !is_blocked(input, &sand, particle + (-1, 1)) { + particle = particle + (-1, 1); + } else if !is_blocked(input, &sand, particle + (1, 1)) { + particle = particle + (1, 1); + } else { + sand.insert(particle); + break; + } + + if particle.y >= abyss_depth { + return sand.len(); + } + } + } +} + +#[aoc(day14, part2)] +fn part2(input: &[Path]) -> usize { + let floor_depth = input + .iter() + .flat_map(|path| path.0.iter().map(|coord| coord.y)) + .max() + .unwrap() + + 2; + + let mut sand = HashSet::new(); + + loop { + let mut particle = Coord { x: 500, y: 0 }; + + loop { + if particle.y + 1 == floor_depth { + sand.insert(particle); + break; + } + + if !is_blocked(input, &sand, particle + (0, 1)) { + particle = particle + (0, 1); + } else if !is_blocked(input, &sand, particle + (-1, 1)) { + particle = particle + (-1, 1); + } else if !is_blocked(input, &sand, particle + (1, 1)) { + particle = particle + (1, 1); + } else { + sand.insert(particle); + + if particle == (Coord { x: 500, y: 0 }) { + return sand.len(); + } + + break; + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9153be2..215a7ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod util; + use aoc_runner_derive::aoc_lib; mod days { @@ -15,6 +17,7 @@ mod days { mod day11; mod day12; mod day13; + mod day14; } aoc_lib! { year = 2022 } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..cf05896 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,10 @@ +use std::str::FromStr; + +pub fn parse_num(s: &str) -> Option<(T, &str)> { + let (num_part, rest) = s + .char_indices() + .find_map(|(i, c)| (!c.is_numeric()).then_some(s.split_at(i))) + .unwrap_or((s, "")); + + Some((num_part.parse().ok()?, rest)) +}