diff --git a/day-10/src/part2.rs b/day-10/src/part2.rs index 1ff3976..5d72cc8 100644 --- a/day-10/src/part2.rs +++ b/day-10/src/part2.rs @@ -1,18 +1,222 @@ use crate::error::AocError; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] +struct Point { + row: i32, + col: i32, +} + +#[derive(Debug, Clone, Eq, PartialEq, Default)] +struct Pipe { + input: Point, + output: Point, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum Tile { + Vertical(Pipe), + Horizontal(Pipe), + NorthEast(Pipe), + NorthWest(Pipe), + SouthWest(Pipe), + SouthEast(Pipe), + Ground, + Start, +} + +fn parse(input: &str) -> BTreeMap { + let grid = input + .lines() + .enumerate() + .flat_map(|(row, line)| { + line.chars().enumerate().map(move |(col, c)| { + let pos = Point { + row: row as i32, + col: col as i32, + }; + + let tile = match c { + '|' => Tile::Vertical(Pipe { + input: Point { + row: pos.row - 1, + col: pos.col, + }, + output: Point { + row: pos.row + 1, + col: pos.col, + }, + }), + '-' => Tile::Horizontal(Pipe { + input: Point { + row: pos.row, + col: pos.col - 1, + }, + output: Point { + row: pos.row, + col: pos.col + 1, + }, + }), + 'L' => Tile::NorthEast(Pipe { + input: Point { + row: pos.row - 1, + col: pos.col, + }, + output: Point { + row: pos.row, + col: pos.col + 1, + }, + }), + 'J' => Tile::NorthWest(Pipe { + input: Point { + row: pos.row - 1, + col: pos.col, + }, + output: Point { + row: pos.row, + col: pos.col - 1, + }, + }), + '7' => Tile::SouthWest(Pipe { + input: Point { + row: pos.row + 1, + col: pos.col, + }, + output: Point { + row: pos.row, + col: pos.col - 1, + }, + }), + 'F' => Tile::SouthEast(Pipe { + input: Point { + row: pos.row + 1, + col: pos.col, + }, + output: Point { + row: pos.row, + col: pos.col + 1, + }, + }), + '.' => Tile::Ground, + 'S' => Tile::Start, + _ => panic!("Unknown tile: {}", c), + }; + + (pos, tile) + }) + }) + .collect::>(); + + grid +} #[tracing::instrument] pub fn process(input: &str) -> miette::Result { - Ok(0) + let grid = parse(input); + + let mut main_loop = BTreeMap::default(); + + let start: &Point = grid + .iter() + .find(|(_, tile)| **tile == Tile::Start) + .map(|(pos, _)| pos) + .unwrap(); + + let start_connects: Vec<&Point> = grid + .iter() + .filter(|(_, tile)| match tile { + Tile::Vertical(pipe) => pipe.input == *start || pipe.output == *start, + Tile::Horizontal(pipe) => pipe.input == *start || pipe.output == *start, + Tile::NorthEast(pipe) => pipe.input == *start || pipe.output == *start, + Tile::NorthWest(pipe) => pipe.input == *start || pipe.output == *start, + Tile::SouthWest(pipe) => pipe.input == *start || pipe.output == *start, + Tile::SouthEast(pipe) => pipe.input == *start || pipe.output == *start, + _ => false, + }) + .map(|(pos, _)| pos) + .collect(); + + let mut current = *start_connects.first().unwrap(); + let mut prev = start; + while current != start { + let tile = grid.get(current).unwrap(); + match tile { + Tile::Vertical(p) + | Tile::Horizontal(p) + | Tile::NorthEast(p) + | Tile::NorthWest(p) + | Tile::SouthEast(p) + | Tile::SouthWest(p) => { + if p.input == *prev { + prev = current; + current = &p.output; + main_loop.insert(current, tile); + main_loop.insert(prev, tile); + } else { + prev = current; + current = &p.input; + main_loop.insert(current, tile); + main_loop.insert(prev, tile); + } + } + _ => panic!("Unknown tile: {:?}", tile), + } + } + + Ok(grid + .iter() + .filter(|(pos, _)| !main_loop.contains_key(pos)) + .filter_map(|(pos, _)| { + let tiles_in_row = main_loop.keys().filter(|p| p.row == pos.row); + let count_west = tiles_in_row + .clone() + .filter(|t| { + matches!( + main_loop.get(*t), + Some(Tile::Vertical(_)) + | Some(Tile::Start) + | Some(Tile::SouthEast(_)) + | Some(Tile::SouthWest(_)) + ) && t.col < pos.col + }) + .count(); + let count_east = tiles_in_row + .clone() + .filter(|t| { + matches!( + main_loop.get(*t), + Some(Tile::Vertical(_)) + | Some(Tile::Start) + | Some(Tile::SouthEast(_)) + | Some(Tile::SouthWest(_)) + ) && t.col > pos.col + }) + .count(); + + if count_west % 2 != 0 && (count_east % 2 != 0 || count_east != 0) { + Some(1) + } else { + None + } + }) + .count() as u64) } #[cfg(test)] mod tests { use super::*; + use rstest::rstest; - #[test_log::test] - fn test_process() -> miette::Result<()> { - let input = include_str!("../test-input.txt"); - assert_eq!(0, process(input)?); + #[test_log::test(rstest)] + #[case("test-input3.txt", 4)] + #[case("test-input4.txt", 8)] + #[case("test-input5.txt", 10)] + fn test_process(#[case] filename: &str, #[case] expected: usize) -> miette::Result<()> { + let input = + String::from_utf8_lossy(&std::fs::read(std::path::Path::new(filename)).unwrap()) + .parse::() + .unwrap(); + assert_eq!(expected, process(&input)? as usize); Ok(()) } } diff --git a/day-10/test-input3.txt b/day-10/test-input3.txt new file mode 100644 index 0000000..6933a28 --- /dev/null +++ b/day-10/test-input3.txt @@ -0,0 +1,9 @@ +........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +........... \ No newline at end of file diff --git a/day-10/test-input4.txt b/day-10/test-input4.txt new file mode 100644 index 0000000..2e5dcbb --- /dev/null +++ b/day-10/test-input4.txt @@ -0,0 +1,10 @@ +.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... \ No newline at end of file diff --git a/day-10/test-input5.txt b/day-10/test-input5.txt new file mode 100644 index 0000000..fbc0300 --- /dev/null +++ b/day-10/test-input5.txt @@ -0,0 +1,10 @@ +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 \ No newline at end of file