diff --git a/Cargo.lock b/Cargo.lock index 9bf2290..ebc3f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,20 +118,6 @@ version = "0.0.0" dependencies = [ "indoc", "itertools", - "pathfinding", - "rayon", -] - -[[package]] -name = "deprecate-until" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" -dependencies = [ - "proc-macro2", - "quote", - "semver", - "syn", ] [[package]] @@ -140,49 +126,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "indoc" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" -[[package]] -name = "integer-sqrt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" -dependencies = [ - "num-traits", -] - [[package]] name = "itertools" version = "0.12.0" @@ -299,39 +248,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "pathfinding" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752866d4511516a35883728309499db42696f388263586b70659a5461e641db5" -dependencies = [ - "deprecate-until", - "fixedbitset", - "indexmap", - "integer-sqrt", - "num-traits", - "rustc-hash", - "thiserror", -] - -[[package]] -name = "proc-macro2" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - [[package]] name = "ranges" version = "0.3.3" @@ -358,57 +274,8 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - -[[package]] -name = "syn" -version = "2.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/day-10/Cargo.toml b/day-10/Cargo.toml index 3a3b669..5c93ccb 100644 --- a/day-10/Cargo.toml +++ b/day-10/Cargo.toml @@ -7,8 +7,6 @@ edition = "2021" [dependencies] itertools.workspace = true -pathfinding = "4.4.0" -rayon.workspace = true [dev-dependencies] indoc.workspace = true diff --git a/day-10/src/bin/part1.rs b/day-10/src/bin/part1.rs index 27c8716..8624f10 100644 --- a/day-10/src/bin/part1.rs +++ b/day-10/src/bin/part1.rs @@ -1,7 +1,6 @@ -use std::{fmt::Debug, sync::Mutex}; +use std::iter; -use pathfinding::prelude::bfs; -use rayon::prelude::*; +use itertools::Itertools; fn main() { println!("{}", part1(include_str!("./input.txt"))); @@ -12,8 +11,8 @@ fn part1(input: &str) -> usize { .lines() .enumerate() .map(|(y, line)| { - line.par_char_indices() - .map(|(x, c)| VPipe::new(x, y, c.try_into().expect("invalid pipe character"))) + line.char_indices() + .map(|(x, c)| Pipe::new(x, y, c.try_into().expect("invalid pipe character"))) .collect() }) .collect::>>(); @@ -24,153 +23,49 @@ fn part1(input: &str) -> usize { .find(|p| p.pipe_type == PipeType::Starting) .expect("no starting pipe found"); - let fist_step = starting_pipe.get_neg_x(&pipes); + let mut starting = Vec::with_capacity(2); + if starting_pipe.pipe_type.has_pos_x() { + if let Some(pipe) = starting_pipe.get_pos_x(&pipes) { + starting.push((Direction::Left, pipe)); + } + } + if starting_pipe.pipe_type.has_neg_x() { + if let Some(pipe) = starting_pipe.get_neg_x(&pipes) { + starting.push((Direction::Right, pipe)); + } + } + if starting_pipe.pipe_type.has_pos_y() { + if let Some(pipe) = starting_pipe.get_pos_y(&pipes) { + starting.push((Direction::Up, pipe)); + } + } + if starting_pipe.pipe_type.has_neg_y() { + if let Some(pipe) = starting_pipe.get_neg_y(&pipes) { + starting.push((Direction::Down, pipe)); + } + } - let result = fist_step - .and_then(|starting_pipe| { - bfs( - &starting_pipe, - |pipe| pipe.successors(&pipes), - |pipe| pipe.pipe_type == PipeType::Starting, - ) - }) - .expect("no loop found"); + let (path_a, path_b) = starting + .into_iter() + .map(|p| iter::successors(Some(p), |(d, p)| p.successor(*d, &pipes))) + .collect_tuple() + .expect("more than 2 paths"); - result.len() / 2 + path_a + .zip(path_b) + .take_while(|((_, pipe_a), (_, pipe_b))| pipe_a != pipe_b) + .count() + + 1 } #[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)] enum Direction { - None, Left, Right, Up, Down, } -struct VPipe(Pipe, Mutex); -impl VPipe { - fn new(x: usize, y: usize, pipe_type: PipeType) -> Self { - Self(Pipe::new(x, y, pipe_type), Mutex::new(Direction::None)) - } - - fn was_visited(&self) -> bool { - self.get_direction() != Direction::None - } - - fn set_direction(&self, direction: Direction) { - *self.1.lock().unwrap() = direction; - } - fn get_direction(&self) -> Direction { - *self.1.lock().unwrap() - } -} -impl std::ops::Deref for VPipe { - type Target = Pipe; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl PartialEq for VPipe { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} -impl Eq for VPipe {} -impl std::hash::Hash for VPipe { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} -impl Debug for VPipe { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("VPipe") - .field("pipe", &self.0) - .field("entered_from", &self.get_direction()) - .finish() - } -} -impl Clone for VPipe { - fn clone(&self) -> Self { - Self( - Pipe::new(self.x, self.y, self.pipe_type), - Mutex::new(self.get_direction()), - ) - } -} - -impl VPipe { - fn successors<'a>(&self, pipes: &'a [Vec]) -> Vec<&'a VPipe> { - vec![ - self.get_pos_x(pipes), - self.get_neg_x(pipes), - self.get_pos_y(pipes), - self.get_neg_y(pipes), - ] - .into_iter() - .flatten() - .collect() - } - - fn get_pos_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a VPipe> { - if self.pipe_type.has_pos_x() && self.get_direction() != Direction::Right { - pipes - .get(self.y)? - .get(self.x + 1) - .filter(|p| p.pipe_type.has_neg_x() && !p.was_visited()) - .map(|p| { - p.set_direction(Direction::Left); - p - }) - } else { - None - } - } - fn get_neg_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a VPipe> { - if self.pipe_type.has_neg_x() && self.get_direction() != Direction::Left { - pipes - .get(self.y)? - .get(self.x.checked_sub(1)?) - .filter(|p| p.pipe_type.has_pos_x() && !p.was_visited()) - .map(|p| { - p.set_direction(Direction::Right); - p - }) - } else { - None - } - } - fn get_pos_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a VPipe> { - if self.pipe_type.has_pos_y() && self.get_direction() != Direction::Down { - pipes - .get(self.y + 1)? - .get(self.x) - .filter(|p| p.pipe_type.has_neg_y() && !p.was_visited()) - .map(|p| { - p.set_direction(Direction::Up); - p - }) - } else { - None - } - } - fn get_neg_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a VPipe> { - if self.pipe_type.has_neg_y() && self.get_direction() != Direction::Up { - pipes - .get(self.y.checked_sub(1)?)? - .get(self.x) - .filter(|p| p.pipe_type.has_pos_y() && !p.was_visited()) - .map(|p| { - p.set_direction(Direction::Down); - p - }) - } else { - None - } - } -} - #[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)] struct Pipe { x: usize, @@ -181,6 +76,65 @@ impl Pipe { fn new(x: usize, y: usize, pipe_type: PipeType) -> Self { Self { x, y, pipe_type } } + + fn successor<'a>( + &self, + entry_direction: Direction, + pipes: &'a [Vec], + ) -> Option<(Direction, &'a Self)> { + if entry_direction != Direction::Right && self.pipe_type.has_pos_x() { + self.get_pos_x(pipes).map(|p| (Direction::Left, p)) + } else if entry_direction != Direction::Left && self.pipe_type.has_neg_x() { + self.get_neg_x(pipes).map(|p| (Direction::Right, p)) + } else if entry_direction != Direction::Up && self.pipe_type.has_neg_y() { + self.get_neg_y(pipes).map(|p| (Direction::Down, p)) + } else if entry_direction != Direction::Down && self.pipe_type.has_pos_y() { + self.get_pos_y(pipes).map(|p| (Direction::Up, p)) + } else { + None + } + } + + fn get_pos_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_pos_x() { + pipes + .get(self.y)? + .get(self.x + 1) + .filter(|p| p.pipe_type.has_neg_x()) + } else { + None + } + } + fn get_neg_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_neg_x() { + pipes + .get(self.y)? + .get(self.x.checked_sub(1)?) + .filter(|p| p.pipe_type.has_pos_x()) + } else { + None + } + } + fn get_pos_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_pos_y() { + pipes + .get(self.y + 1)? + .get(self.x) + .filter(|p| p.pipe_type.has_neg_y()) + } else { + None + } + } + fn get_neg_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_neg_y() { + pipes + .get(self.y.checked_sub(1)?)? + .get(self.x) + .filter(|p| p.pipe_type.has_pos_y()) + } else { + None + } + } } #[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)] @@ -246,7 +200,6 @@ mod tests { use indoc::indoc; #[test] - #[ignore = "wrong starting pipe chosen in code"] fn test_part1() { assert_eq!( part1(indoc!( diff --git a/day-10/src/bin/part2.rs b/day-10/src/bin/part2.rs new file mode 100644 index 0000000..0a05f61 --- /dev/null +++ b/day-10/src/bin/part2.rs @@ -0,0 +1,301 @@ +use std::{collections::BTreeMap, iter}; + +use itertools::Itertools; + +fn main() { + println!("{}", part2(include_str!("./input.txt"))); +} + +fn part2(input: &str) -> usize { + let pipes = input + .lines() + .enumerate() + .map(|(y, line)| { + line.char_indices() + .map(|(x, c)| Pipe::new(x, y, c.try_into().expect("invalid pipe character"))) + .collect() + }) + .collect::>>(); + + let starting_pipe = pipes + .iter() + .flatten() + .find(|p| p.pipe_type == PipeType::Starting) + .expect("no starting pipe found"); + + let mut starting_directions = Vec::new(); + + let mut starting = Vec::with_capacity(2); + if starting_pipe.pipe_type.has_pos_x() { + if let Some(pipe) = starting_pipe.get_pos_x(&pipes) { + starting.push((Direction::Left, pipe)); + starting_directions.push(Direction::Right); + } + } + if starting_pipe.pipe_type.has_neg_x() { + if let Some(pipe) = starting_pipe.get_neg_x(&pipes) { + starting.push((Direction::Right, pipe)); + starting_directions.push(Direction::Left); + } + } + if starting_pipe.pipe_type.has_pos_y() { + if let Some(pipe) = starting_pipe.get_pos_y(&pipes) { + starting.push((Direction::Up, pipe)); + starting_directions.push(Direction::Down); + } + } + if starting_pipe.pipe_type.has_neg_y() { + if let Some(pipe) = starting_pipe.get_neg_y(&pipes) { + starting.push((Direction::Down, pipe)); + starting_directions.push(Direction::Up); + } + } + + let (path_a, path_b) = starting + .into_iter() + .map(|p| iter::successors(Some(p), |(d, p)| p.successor(*d, &pipes))) + .collect_tuple() + .expect("more than 2 paths"); + + let starting_pipe_type = match starting_directions + .into_iter() + .collect_tuple() + .expect("more than than two paths") + { + (Direction::Down, Direction::Up) => PipeType::Vertical, + (Direction::Right, Direction::Left) => PipeType::Horizontal, + (Direction::Right, Direction::Down) => PipeType::SouthEast, + (Direction::Right, Direction::Up) => PipeType::NorthEast, + (Direction::Left, Direction::Down) => PipeType::SouthWest, + (Direction::Left, Direction::Up) => PipeType::NorthWest, + _ => unreachable!("pipe type does not exist"), + }; + + let mut path_elements = BTreeMap::new(); + path_elements.insert((starting_pipe.x, starting_pipe.y), starting_pipe_type); + + let (a, b): (Vec<_>, Vec<_>) = path_a + .zip(path_b) + .take_while(|((_, pipe_a), (_, pipe_b))| pipe_a != pipe_b) + .unzip(); + let mut path = a.into_iter().chain(b).collect::>(); + let (last_direction, last_pipe) = path.last().expect("no last element"); + path.push( + last_pipe + .successor(*last_direction, &pipes) + .expect("no successor"), + ); + path.iter().for_each(|(_, pipe)| { + path_elements.insert((pipe.x, pipe.y), pipe.pipe_type); + }); + + let width = pipes.get(0).expect("no pipes").len(); + + let inner = pipes + .iter() + .flatten() + .filter(|p| { + if path_elements.contains_key(&(p.x, p.y)) { + false + } else { + let amount = (p.x..width) + .filter(|x| { + let pipe = path_elements.get(&(*x, p.y)); + pipe.map(|pipe| { + matches!( + *pipe, + PipeType::Vertical | PipeType::NorthEast | PipeType::NorthWest + ) + }) + .unwrap_or(false) + }) + .count(); + + amount % 2 == 1 + } + }) + .collect::>(); + + inner.len() +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)] +enum Direction { + Left, + Right, + Up, + Down, +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)] +struct Pipe { + x: usize, + y: usize, + pipe_type: PipeType, +} +impl Pipe { + fn new(x: usize, y: usize, pipe_type: PipeType) -> Self { + Self { x, y, pipe_type } + } + + fn successor<'a>( + &self, + entry_direction: Direction, + pipes: &'a [Vec], + ) -> Option<(Direction, &'a Self)> { + if entry_direction != Direction::Right && self.pipe_type.has_pos_x() { + self.get_pos_x(pipes).map(|p| (Direction::Left, p)) + } else if entry_direction != Direction::Left && self.pipe_type.has_neg_x() { + self.get_neg_x(pipes).map(|p| (Direction::Right, p)) + } else if entry_direction != Direction::Up && self.pipe_type.has_neg_y() { + self.get_neg_y(pipes).map(|p| (Direction::Down, p)) + } else if entry_direction != Direction::Down && self.pipe_type.has_pos_y() { + self.get_pos_y(pipes).map(|p| (Direction::Up, p)) + } else { + None + } + } + + fn get_pos_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_pos_x() { + pipes + .get(self.y)? + .get(self.x + 1) + .filter(|p| p.pipe_type.has_neg_x()) + } else { + None + } + } + fn get_neg_x<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_neg_x() { + pipes + .get(self.y)? + .get(self.x.checked_sub(1)?) + .filter(|p| p.pipe_type.has_pos_x()) + } else { + None + } + } + fn get_pos_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_pos_y() { + pipes + .get(self.y + 1)? + .get(self.x) + .filter(|p| p.pipe_type.has_neg_y()) + } else { + None + } + } + fn get_neg_y<'a>(&self, pipes: &'a [Vec]) -> Option<&'a Pipe> { + if self.pipe_type.has_neg_y() { + pipes + .get(self.y.checked_sub(1)?)? + .get(self.x) + .filter(|p| p.pipe_type.has_pos_y()) + } else { + None + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)] +enum PipeType { + Empty, + Starting, + Horizontal, + Vertical, + NorthEast, + NorthWest, + SouthWest, + SouthEast, +} + +impl PipeType { + fn has_pos_x(&self) -> bool { + matches!( + self, + Self::Horizontal | Self::NorthEast | Self::SouthEast | Self::Starting + ) + } + fn has_neg_x(&self) -> bool { + matches!( + self, + Self::Horizontal | Self::NorthWest | Self::SouthWest | Self::Starting + ) + } + fn has_pos_y(&self) -> bool { + matches!( + self, + Self::Vertical | Self::SouthEast | Self::SouthWest | Self::Starting + ) + } + fn has_neg_y(&self) -> bool { + matches!( + self, + Self::Vertical | Self::NorthEast | Self::NorthWest | Self::Starting + ) + } +} + +impl TryFrom for PipeType { + type Error = (); + + fn try_from(c: char) -> Result { + match c { + '.' => Ok(Self::Empty), + 'S' => Ok(Self::Starting), + '-' => Ok(Self::Horizontal), + '|' => Ok(Self::Vertical), + 'L' => Ok(Self::NorthEast), + 'J' => Ok(Self::NorthWest), + '7' => Ok(Self::SouthWest), + 'F' => Ok(Self::SouthEast), + _ => Err(()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn test_part2() { + // assert_eq!( + // part2(indoc!( + // " + // FF7FSF7F7F7F7F7F---7 + // L|LJ||||||||||||F--J + // FL-7LJLJ||||||LJL-77 + // F--JF--7||LJLJ7F7FJ- + // L---JF-JLJ.||-FJLJJ7 + // |F|F-JF---7F7-L7L|7| + // |FFJF7L7F-JF7|JL---7 + // 7-L-JL7||F7|L7F-7F7| + // L.L7LFJ|||||FJL7||LJ + // L7JLJL-JLJLJL--JLJ.L + // " + // )), + // 10 + // ); + + assert_eq!( + part2(indoc!( + " + .F----7F7F7F7F-7.... + .|F--7||||||||FJ.... + .||.FJ||||||||L7.... + FJL7L7LJLJ||LJ.L-7.. + L--J.L7...LJS7F-7L7. + ....F-J..F7FJ|L7L7L7 + ....L7.F7||L7|.L7L7| + .....|FJLJ|FJ|F7|.LJ + ....FJL-7.||.||||... + ....L---J.LJ.LJLJ... + " + )), + 8 + ) + } +}