diff --git a/Cargo.lock b/Cargo.lock index 379ec3a..33f01d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,7 @@ dependencies = [ "regex", "tracing", "tracing-subscriber", + "tracing-test", ] [[package]] @@ -133,6 +134,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -217,8 +227,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -229,9 +248,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -345,14 +370,39 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 4d3ca00..60c68c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ color-eyre = "0.6.3" regex = "1.11.1" tracing = "0.1.41" tracing-subscriber = "0.3.19" +tracing-test = "0.2.5" diff --git a/inputs/day06/example.txt b/inputs/day06/example.txt new file mode 100644 index 0000000..b60e466 --- /dev/null +++ b/inputs/day06/example.txt @@ -0,0 +1,10 @@ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... \ No newline at end of file diff --git a/src/day06.rs b/src/day06.rs new file mode 100644 index 0000000..19e870c --- /dev/null +++ b/src/day06.rs @@ -0,0 +1,216 @@ +use std::collections::HashSet; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +enum Direction { + Up, + Right, + Down, + Left, +} +impl Direction { + fn next(self) -> Self { + match self { + Direction::Up => Direction::Right, + Direction::Right => Direction::Down, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + } + } +} +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Field { + Empty, + Blocked, + Guard(Direction), +} +fn parse_field(c: char) -> Field { + match c { + '.' => Field::Empty, + '^' => Field::Guard(Direction::Up), + '>' => Field::Guard(Direction::Right), + 'v' => Field::Guard(Direction::Down), + '<' => Field::Guard(Direction::Left), + '#' => Field::Blocked, + c => panic!("Unknown field: {c}"), + } +} + +pub fn pt1(input: &str) -> usize { + let mut visited_fields: HashSet<(usize, usize)> = HashSet::new(); + let mut current_pos = (0, 0); + let mut current_direction = Direction::Up; + let grid: Vec<_> = parse_grid(input, &mut current_direction, &mut current_pos); + + loop { + tracing::debug!("{current_pos:?} [{current_direction:?}]"); + let next_pos: (usize, usize) = match current_direction { + Direction::Up => { + if current_pos.1 == 0 { + visited_fields.insert(current_pos); + break; + } + (current_pos.0, current_pos.1 - 1) + } + Direction::Right => { + if grid[current_pos.1].len() - 1 == current_pos.0 { + visited_fields.insert(current_pos); + break; + } + (current_pos.0 + 1, current_pos.1) + } + Direction::Down => { + if grid.len() - 1 == current_pos.1 { + visited_fields.insert(current_pos); + break; + } + (current_pos.0, current_pos.1 + 1) + } + Direction::Left => { + if current_pos.0 == 0 { + visited_fields.insert(current_pos); + break; + } + (current_pos.0 - 1, current_pos.1) + } + }; + match grid + .get(next_pos.1) + .and_then(|l| l.get(next_pos.0)) + .unwrap() + { + Field::Empty => { + visited_fields.insert(current_pos); + current_pos = next_pos; + continue; + } + Field::Blocked => { + current_direction = current_direction.next(); + continue; + } + Field::Guard(_) => unreachable!(), + } + } + visited_fields.len() +} + +pub fn pt2(input: &str) -> usize { + let mut initial_position = (0, 0); + let mut initial_direction = Direction::Up; + let original_grid: Vec<_> = parse_grid(input, &mut initial_direction, &mut initial_position); + + let mut result = 0; + for (y, row) in original_grid.iter().enumerate() { + for (x, _) in row.iter().enumerate() { + if (x, y) == initial_position { + continue; + } + let mut grid = original_grid.clone(); + grid[y][x] = Field::Blocked; + let mut current_pos = initial_position; + let mut current_direction = initial_direction; + let mut visited_fields: HashSet<((usize, usize), Direction)> = HashSet::new(); + let mut is_valid = true; + 'current_attempt: loop { + tracing::debug!("{current_pos:?} [{current_direction:?}]"); + if !visited_fields.insert((current_pos, current_direction)) { + is_valid = false; + break 'current_attempt; + } + let next_pos = match current_direction { + Direction::Up => { + if current_pos.1 == 0 { + break 'current_attempt; + } + (current_pos.0, current_pos.1 - 1) + } + Direction::Right => { + if grid[current_pos.1].len() - 1 == current_pos.0 { + break 'current_attempt; + } + (current_pos.0 + 1, current_pos.1) + } + Direction::Down => { + if grid.len() - 1 == current_pos.1 { + break 'current_attempt; + } + (current_pos.0, current_pos.1 + 1) + } + Direction::Left => { + if current_pos.0 == 0 { + break 'current_attempt; + } + (current_pos.0 - 1, current_pos.1) + } + }; + match grid + .get(next_pos.1) + .and_then(|l| l.get(next_pos.0)) + .unwrap() + { + Field::Empty => { + current_pos = next_pos; + continue 'current_attempt; + } + Field::Blocked => { + current_direction = current_direction.next(); + continue 'current_attempt; + } + Field::Guard(_) => unreachable!(), + } + } + if !is_valid { + result += 1; + }; + } + } + result +} + +fn parse_grid( + input: &str, + initial_direction: &mut Direction, + initial_position: &mut (usize, usize), +) -> Vec> { + input + .lines() + .enumerate() + .map(|(row_index, line)| { + line.chars() + .enumerate() + .map(|(x, c)| match parse_field(c) { + Field::Empty => Field::Empty, + Field::Blocked => Field::Blocked, + Field::Guard(direction) => { + *initial_direction = direction; + *initial_position = (x, row_index); + Field::Empty + } + }) + .collect::>() + }) + .collect() +} + +#[cfg(test)] +mod tests { + use std::fs::read_to_string; + + use tracing_test::traced_test; + + use super::{pt1, pt2}; + + #[traced_test] + #[test] + pub fn test_pt1() { + let input = + read_to_string("./inputs/day06/example.txt").expect("Missing example.txt for day06"); + assert_eq!(pt1(&input), 41) + } + #[test] + pub fn test_pt2() { + let input = + read_to_string("./inputs/day06/example.txt").expect("Missing example.txt for day06"); + + assert_eq!(pt2(&input), 6) + } +} diff --git a/src/main.rs b/src/main.rs index 04529fa..b1f4ea3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,15 +5,17 @@ mod day02; mod day03; mod day04; mod day05; +mod day06; fn main() { color_eyre::install().unwrap(); - tracing_subscriber::fmt::init(); + tracing_subscriber::fmt().init(); solve_day(1, day01::pt1, day01::pt2); solve_day(2, day02::pt1, day02::pt2); solve_day(3, day03::pt1, day03::pt2); solve_day(4, day04::pt1, day04::pt2); solve_day(5, day05::pt1, day05::pt2); + solve_day(6, day06::pt1, day06::pt2); } fn solve_day(day: u8, part_a: Fa, part_b: Fb)