From e46cd60df15d23a54ed31cb4489c8def145fd622 Mon Sep 17 00:00:00 2001 From: Louis Vallat Date: Sat, 28 Dec 2024 18:19:41 +0100 Subject: [PATCH] feat: added day 18 part 1 and 2 Signed-off-by: Louis Vallat --- .gitea/workflows/build_and_run.yml | 2 +- day18/Cargo.toml | 6 ++ day18/src/main.rs | 89 ++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 day18/Cargo.toml create mode 100644 day18/src/main.rs diff --git a/.gitea/workflows/build_and_run.yml b/.gitea/workflows/build_and_run.yml index 1670e95..d7b1314 100644 --- a/.gitea/workflows/build_and_run.yml +++ b/.gitea/workflows/build_and_run.yml @@ -11,7 +11,7 @@ jobs: name: Challenge for day strategy: matrix: - day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] + day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] runs-on: rust-bookworm steps: - name: Check out repository code diff --git a/day18/Cargo.toml b/day18/Cargo.toml new file mode 100644 index 0000000..ebcf320 --- /dev/null +++ b/day18/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day18" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day18/src/main.rs b/day18/src/main.rs new file mode 100644 index 0000000..a47d4e0 --- /dev/null +++ b/day18/src/main.rs @@ -0,0 +1,89 @@ +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::{env, fs}; + +const MAP_SIZE: i32 = 71; +const SLICE_SIZE: usize = 1024; + +fn read_input(path: &str) -> String { + fs::read_to_string(path).expect("Cannot read file.") +} + +fn parse_input(input: &str) -> Vec<(i32, i32)> { + let mut falling_bytes = Vec::new(); + + for line in input.lines().filter(|s| !s.is_empty()) { + let (x, y) = line.split_once(',').unwrap(); + falling_bytes.push((x.parse().unwrap(), y.parse().unwrap())); + } + + falling_bytes +} + +fn shortest_path(corrupted: &HashSet<(i32, i32)>) -> Option { + let mut positions_cost = HashMap::new(); + positions_cost.insert((0, 0), 0); + + let mut queue = BTreeSet::new(); + queue.insert((0, 0, 0)); + while let Some((cost, x, y)) = queue.pop_first() { + if cost > *positions_cost.get(&(x, y)).unwrap() { + continue; + } + + for (dx, dy) in [(0, -1), (0, 1), (-1, 0), (1, 0)] { + if !(0..MAP_SIZE).contains(&(x + dx)) || !(0..MAP_SIZE).contains(&(y + dy)) { + continue; + } + + if corrupted.contains(&(x + dx, y + dy)) { + continue; + } + + if (cost + 1) < *positions_cost.get(&(x + dx, y + dy)).unwrap_or(&usize::MAX) { + positions_cost.insert((x + dx, y + dy), cost + 1); + queue.insert((cost + 1, x + dx, y + dy)); + } + } + } + + positions_cost.get(&(MAP_SIZE - 1, MAP_SIZE - 1)).copied() +} + +fn find_blocker(corruptions: &Vec<(i32, i32)>) -> (i32, i32) { + let (mut low, mut high) = (SLICE_SIZE, corruptions.len() - 1); + while (high - low) > 1 { + let middle = low + (high - low) / 2; + let corrupted = HashSet::from_iter(corruptions[0..=middle].iter().copied()); + if shortest_path(&corrupted).is_none() { + high = middle; + } else { + low = middle; + } + } + + for i in low..=high { + let corrupted = HashSet::from_iter(corruptions[0..=i].iter().copied()); + if shortest_path(&corrupted).is_none() { + return corruptions[i]; + } + } + (0, 0) +} + +fn main() { + let args: Vec = env::args().collect(); + for arg in args.iter().skip(1) { + let input = read_input(&arg); + let falling_bytes = parse_input(&input); + let blocker = find_blocker(&falling_bytes); + println!("[{}]", &arg); + println!( + "\t[Part 1] => Answer is '{}'.", + shortest_path(&HashSet::from_iter( + falling_bytes[0..SLICE_SIZE].iter().cloned() + )) + .unwrap_or(usize::MAX) + ); + println!("\t[Part 2] => Answer is '{},{}'.", blocker.0, blocker.1); + } +}