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);
+ }
}