diff --git a/rust/2023/Cargo.lock b/rust/2023/Cargo.lock index 5fa4b49..6166d26 100644 --- a/rust/2023/Cargo.lock +++ b/rust/2023/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "nom", "rayon", "regex", + "rstest", ] [[package]] @@ -103,6 +104,107 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "itertools" version = "0.12.0" @@ -149,6 +251,18 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -216,6 +330,50 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.39", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.15" @@ -228,6 +386,12 @@ 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 = "serde" version = "1.0.193" @@ -259,6 +423,15 @@ dependencies = [ "serde", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/rust/2023/Cargo.toml b/rust/2023/Cargo.toml index 5ecaa8e..fe4c1c3 100644 --- a/rust/2023/Cargo.toml +++ b/rust/2023/Cargo.toml @@ -11,3 +11,5 @@ itertools = "0.12.0" regex = "1" rayon = "1.8.0" +[dev-dependencies] +rstest = "0.18.2" diff --git a/rust/2023/README.md b/rust/2023/README.md index 2df3dc5..c6f9700 100644 --- a/rust/2023/README.md +++ b/rust/2023/README.md @@ -25,7 +25,7 @@ You try to ask why they can't just use a weather machine ("not powerful enough") | [Day 7: Camel Cards](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_07.rs) | 🌟 | 250474325 | 🌟 | 248909434 | | [Day 8: Haunted Wasteland](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_08.rs) | 🌟 | 22411 | 🌟 | 11188774513823 | | [Day 9: Mirage Maintenance](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_09.rs) | 🌟 | 1853145119 | 🌟 | 923 | -| [Day 10: Pipe Maze](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_10.rs) | 🌟 | 6882 | | | +| [Day 10: Pipe Maze](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_10.rs) | 🌟 | 6882 | 🌟 | 491 | | [Day 11: Cosmic Expansion](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_11.rs) | 🌟 | 9795148 | 🌟 | 650672493820 | ## Performance @@ -43,7 +43,7 @@ With the help of [cargo-aoc](https://github.com/gobanos/cargo-aoc) I get automat | 7 | 364.18 µs | 359.22 µs | | 318.23 µs / 324.96 µs | | 8 | 926.09 µs | 4.47 ms | - / `-70.69%` | 137.33 µs | | 9 | 2.44 µs | 2.55 µs | | 434.07 µs | -| 10 | 1.70 ms | | | 158.20 µs | +| 10 | 1.70 ms | 48.37 µs | | 158.20 µs | | 11 | 846.06 µs | 844.65 µs | | 75.211 µs | \* compared to first solution
diff --git a/rust/2023/src/day_10.rs b/rust/2023/src/day_10.rs index a505029..381620d 100644 --- a/rust/2023/src/day_10.rs +++ b/rust/2023/src/day_10.rs @@ -1,18 +1,41 @@ //! Day 10: Pipe Maze //! +//! The first part is done using Breadth-first search (BFS). We start at one point +//! and look in all directions for valid next points. The valid points are added to +//! a queue, and all points are marked as seen. Keep picking of the queue until +//! there are no items left. +//! //! I had the correct calculations for part 1 for a long time, but I was missing //! a check for the valid start direction. I was checking all directions //! around the start. So, I just added the allowed start directions manually to //! get the right answer. Then I created a real solution for it. //! -//! I don't have the energy to do part 2 right now. I might come back to it later. //! Did a refactor of the code using a new grid implementation that combines with my //! point helper. I think it's a lot cleaner now. +//! +//! Update: +//! I had no idea how to solve part 2. I had to look at the solutions thread for help. +//! I first tried expanding the grid and doing a flood fill, but that turned into a mess. +//! +//! Then I found a clean solution by @maneatingape that uses uses the +//! [Shoelace formula](https://en.wikipedia.org/wiki/Shoelace_formula) +//! and [Pick's theorem](https://en.wikipedia.org/wiki/Pick%27s_theorem). I had never heard +//! of these before, and I don't completely understand them yet. But I'm happy to have +//! learned something new. +//! +//! Starting at `S` find the loop. Each corner piece (`7`, `F`, `J`, `L` and finally `S`) is +//! considered a vertex and added to the running total for the area using the Shoelace +//! formula. Additionally we keep track of the perimeter length. +//! +//! The answer for part two is the number of interior points. Rearranging Pick's theorem: +//! +//! `A = i + b / 2 - 1` +//! => `i = A - b / 2 + 1` +//! +//! This solution can be used for both parts and is much faster. +//! But, I like the BFS solution for part 1, so I'm keeping it. -use crate::{ - grid::Grid, - point::{Point, DOWN, LEFT, RIGHT, UP}, -}; +use crate::{grid::Grid, point::*}; use std::collections::{HashSet, VecDeque}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -58,6 +81,24 @@ impl Tile { } } +// +impl std::fmt::Display for Tile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Tile::*; + + match self { + Vertical => write!(f, "|"), + Horizontal => write!(f, "-"), + NorthEast => write!(f, "L"), + NorthWest => write!(f, "J"), + SouthEast => write!(f, "F"), + SouthWest => write!(f, "7"), + Start => write!(f, "S"), + Ground => write!(f, "."), + } + } +} + impl From for Tile { fn from(value: u8) -> Self { match value { @@ -103,6 +144,7 @@ pub fn input_generator(input: &str) -> Input { /* Part One * +* Follow pipes along a grid and find the furthest point from the start. * */ // Your puzzle answer was @@ -157,23 +199,66 @@ pub fn solve_part_01(input: &Input) -> u64 { /* Part Two * +* Find how many points are enclosed by the loop. * */ -/* -#[doc = r#"``` -use advent_of_code_2023::day_10::*; -let data = include_str!("../input/2023/day10.txt"); -assert_eq!(solve_part_02(&input_generator(data)), 0); -```"#] -*/ #[aoc(day10, part2)] -pub fn solve_part_02(_input: &Input) -> u64 { - 0 +pub fn solve_part_02(input: &Input) -> i32 { + let Input { grid, start } = input; + let determinant = |a: Point, b: Point| a.x * b.y - a.y * b.x; + + // Find start point and direction + let mut corner = *start; + let mut direction = if start.y > 0 { + match grid[corner + UP] { + Tile::Vertical | Tile::SouthWest | Tile::SouthEast => UP, + _ => DOWN, + } + } else { + match grid[corner + DOWN] { + Tile::Vertical | Tile::NorthWest | Tile::NorthEast => DOWN, + _ => UP, + } + }; + let mut position = corner + direction; + let mut steps = 1; + let mut area = 0; + + loop { + // Following vertical/horizontal paths + while grid[position] == Tile::Vertical || grid[position] == Tile::Horizontal { + position += direction; + steps += 1; + } + + // Change direction if we hit a corner + direction = match grid[position] { + Tile::SouthWest if direction == UP => LEFT, + Tile::SouthEast if direction == UP => RIGHT, + Tile::NorthWest if direction == DOWN => LEFT, + Tile::NorthEast if direction == DOWN => RIGHT, + Tile::NorthWest | Tile::NorthEast => UP, + Tile::SouthWest | Tile::SouthEast => DOWN, + _ => { + // We're back at the start + area += determinant(corner, position); + break; + } + }; + + area += determinant(corner, position); + corner = position; + position += direction; + steps += 1; + } + + area.abs() / 2 - steps / 2 + 1 } #[cfg(test)] mod tests { use super::*; + use rstest::rstest; const DATA: &str = "..... .S-7. @@ -196,4 +281,47 @@ LJ..."; assert_eq!(solve_part_01(&input_generator(data)), 8); } + + #[rstest] + #[case( + "........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +|-FJL-|7||-", + 4 + )] + #[case( + ".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 + )] + #[case( + "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 + )] + fn sample_02(#[case] input: &str, #[case] output: i32) { + assert_eq!(solve_part_02(&input_generator(input)), output); + } }