diff --git a/Cargo.lock b/Cargo.lock index 8f183dc..1bb8fc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "advent" version = "0.1.0" dependencies = [ "dotenvy", + "rayon", "regex", "reqwest", ] @@ -117,6 +118,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "displaydoc" version = "0.2.5" @@ -134,6 +160,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -744,6 +776,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" diff --git a/Cargo.toml b/Cargo.toml index b7be0c2..6d1775b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ edition = "2021" [dependencies] dotenvy = "0.15.7" +rayon = "1.10.0" regex = "1.11.1" reqwest = { version = "0.12.9", features = ["blocking"] } diff --git a/src/day6.rs b/src/day6.rs index beefd73..2674075 100644 --- a/src/day6.rs +++ b/src/day6.rs @@ -1,33 +1,21 @@ +use rayon::prelude::*; + pub fn day6() { - let input = crate::input(6); + let (world, start) = World::from_string(&crate::input(6)); - let (world, cursor_location) = World::from_string(&input); - let start = Transform::new( - cursor_location.0, - cursor_location.1, - Direction::North, - &world, - ); + let (mut path, _) = traverse(start, &world); - let mut path = traverse(start).unwrap(); - - path.sort_by(|a, b| { - if a.x == b.x { - a.y.cmp(&b.y) - } else { - a.x.cmp(&b.x) - } - }); - path.dedup_by(|a, b| a.x == b.x && a.y == b.y); + path.sort_by_key(|t| (t.x, t.y)); + path.dedup_by_key(|t| (t.x, t.y)); let total_traveled = path.len(); let loops = path - .iter() + .par_iter() .filter(|p| { let mut new_world = world.clone(); new_world.map[p.y][p.x] = Terrain::Blocked; - let start = Transform::new(start.x, start.y, start.direction, &new_world); - traverse(start).is_none() + let (_, is_loop) = traverse(start, &new_world); + is_loop }) .count(); @@ -35,69 +23,58 @@ pub fn day6() { println!("Total Loopable Positions: {}", loops); } -#[derive(Copy, Clone)] -struct Transform<'a> { +fn traverse(start: Transform, world: &World) -> (Vec, bool) { + let mut positions = vec![start]; + let mut current = start; + + while let Some(next) = current.travel(world) { + if positions.contains(&next) { + return (positions, true); + } + positions.push(next); + current = next; + } + (positions, false) +} + +#[derive(PartialEq, Eq, Copy, Clone)] +struct Transform { x: usize, y: usize, direction: Direction, - world: &'a World, } -impl<'a> Transform<'a> { - fn new(x: usize, y: usize, direction: Direction, world: &'a World) -> Self { - Self { - x, - y, - direction, - world, - } +impl Transform { + fn new(x: usize, y: usize, direction: Direction) -> Self { + Self { x, y, direction } } fn turn(&self) -> Self { - Self { - x: self.x, - y: self.y, - direction: self.direction.turn(), - world: self.world, - } + let direction = match self.direction { + Direction::North => Direction::East, + Direction::East => Direction::South, + Direction::South => Direction::West, + Direction::West => Direction::North, + }; + Self::new(self.x, self.y, direction) } - fn travel(&self) -> Option { - let t = match self.direction { - Direction::North => Some(Self { - x: self.x, - y: self.y.checked_sub(1)?, - direction: self.direction, - world: self.world, - }), - Direction::South => Some(Self { - x: self.x, - y: self.y.checked_add(1)?, - direction: self.direction, - world: self.world, - }), - Direction::East => Some(Self { - x: self.x.checked_add(1)?, - y: self.y, - direction: self.direction, - world: self.world, - }), - Direction::West => Some(Self { - x: self.x.checked_sub(1)?, - y: self.y, - direction: self.direction, - world: self.world, - }), - }?; - - if t.y >= t.world.map.len() || t.x >= t.world.map[0].len() { - return None; + fn travel(&self, world: &World) -> Option { + let (dx, dy) = match self.direction { + Direction::North => (0, -1), + Direction::South => (0, 1), + Direction::East => (1, 0), + Direction::West => (-1, 0), }; - if self.world.terrain_at(&t) == Terrain::Blocked { - Some(self.turn()) - } else { - Some(t) + let new_x = (self.x as i32 + dx).try_into().ok()?; + let new_y = (self.y as i32 + dy).try_into().ok()?; + let next = Self::new(new_x, new_y, self.direction); + + match world.terrain_at(&next) { + Some(Terrain::Blocked) => Some(self.turn()), + Some(Terrain::Free) => Some(next), + None => None, } } } @@ -108,75 +85,45 @@ struct World { } impl World { - fn from_string(input: &str) -> (Self, (usize, usize)) { - let mut map: Vec> = Vec::new(); - let mut cursor: (usize, usize) = (0, 0); - for (y, line) in input.lines().enumerate() { - let row: Vec = line - .chars() - .map(|c| match c { - '#' => Terrain::Blocked, - _ => Terrain::Free, - }) - .collect(); - map.push(row); - if let Some(x) = line.find('^') { - cursor = (x, y); - }; - } - (Self { map }, cursor) + fn from_string(input: &str) -> (Self, Transform) { + let mut start = Transform::new(0, 0, Direction::North); + let map = input + .lines() + .enumerate() + .map(|(y, line)| { + if let Some(x) = line.find('^') { + start = Transform::new(x, y, Direction::North); + } + line.chars() + .map(|c| { + if c == '#' { + Terrain::Blocked + } else { + Terrain::Free + } + }) + .collect() + }) + .collect(); + + (Self { map }, start) } - fn terrain_at(&self, at: &Transform) -> Terrain { - self.map[at.y][at.x] + fn terrain_at(&self, at: &Transform) -> Option { + self.map.get(at.y)?.get(at.x).copied() } } -fn traverse<'a>(start: Transform<'a>) -> Option>> { - let mut cursor = start; - let mut traversed: Vec = vec![cursor]; - while let Some(new_cursor) = cursor.travel() { - if traversed.contains(&new_cursor) { - return None; - } - cursor = new_cursor; - traversed.push(cursor); - } - Some(traversed) -} - #[derive(PartialEq, Eq, Copy, Clone)] enum Terrain { Free, Blocked, } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone)] enum Direction { North, South, East, West, } - -impl Direction { - fn turn(&self) -> Self { - match self { - Self::North => Self::East, - Self::East => Self::South, - Self::South => Self::West, - Self::West => Self::North, - } - } -} - -impl<'a> PartialEq for Transform<'a> { - fn eq(&self, other: &Self) -> bool { - self.x == other.x - && self.y == other.y - && self.direction == other.direction - && std::ptr::eq(self.world, other.world) - } -} - -impl<'a> Eq for Transform<'a> {}