From df01d5a2fb20674756a69d261354e79d970473a2 Mon Sep 17 00:00:00 2001 From: "Jyotirmoy Bandyopadhyaya [Bravo68]" Date: Fri, 22 Dec 2023 23:13:35 +0530 Subject: [PATCH] chore: added rust aoc sols --- Cargo.lock | 24 +++++ code/rs/Cargo.toml | 12 ++- code/rs/README.md | 4 +- code/rs/src/d21.rs | 143 ++++++++++++++++++++++++++++ code/rs/src/d22.rs | 221 ++++++++++++++++++++++++++++++++++++++++++++ code/rs/src/lib.rs | 4 +- code/rs/src/main.rs | 2 + 7 files changed, 407 insertions(+), 3 deletions(-) create mode 100644 code/rs/src/d21.rs create mode 100644 code/rs/src/d22.rs diff --git a/Cargo.lock b/Cargo.lock index f765f5b..dbb63ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,8 @@ name = "aoc-2023" version = "0.1.0" dependencies = [ "itertools", + "lazy_static", + "nom", "num", "pathfinding", "rayon", @@ -143,6 +145,12 @@ dependencies = [ "either", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.151" @@ -174,6 +182,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num" version = "0.4.1" diff --git a/code/rs/Cargo.toml b/code/rs/Cargo.toml index 2c424fc..5d6c8f9 100644 --- a/code/rs/Cargo.toml +++ b/code/rs/Cargo.toml @@ -11,6 +11,8 @@ num = "0.4.1" pathfinding = "4.4.0" rayon = "1.8.0" static_init = "1.0" +lazy_static = "1.4.0" +nom = "7" [profile.release] lto = "fat" @@ -95,4 +97,12 @@ path = "src/d19.rs" [[bin]] name = "day20" -path = "src/d20.rs" \ No newline at end of file +path = "src/d20.rs" + +[[bin]] +name = "day21" +path = "src/d21.rs" + +[[bin]] +name = "day22" +path = "src/d22.rs" \ No newline at end of file diff --git a/code/rs/README.md b/code/rs/README.md index 409067d..3388543 100644 --- a/code/rs/README.md +++ b/code/rs/README.md @@ -24,7 +24,9 @@ This is my repository for the [Advent of Code 2023](https://adventofcode.com/202 | Day 18 | [Check Solution](src/d18.rs) | | Day 19 | [Check Solution](src/d19.rs) | | Day 20 | [Check Solution](src/d20.rs) | -| Day 21 | Not Released | +| Day 21 | [Check Solution](src/d21.rs) | +| Day 22 | [Check Solution](src/d22.rs) | +| Day 23 | Not Released | ## How to run diff --git a/code/rs/src/d21.rs b/code/rs/src/d21.rs new file mode 100644 index 0000000..450513c --- /dev/null +++ b/code/rs/src/d21.rs @@ -0,0 +1,143 @@ +#![allow(clippy::must_use_candidate, clippy::missing_panics_doc, clippy::identity_op)] +use std::collections::hash_map::Entry::Vacant; +use std::collections::{HashMap, HashSet, VecDeque}; +use lazy_static::lazy_static; + +struct Garden { + rocks: HashSet<(isize, isize)>, +} + +fn parse(input: &str) -> (Garden, (isize, isize)) { + let width = input.lines().next().unwrap().len(); + let height = input.lines().count(); + + let input = input.replace('\n', ""); + + let start = input + .chars() + .enumerate() + .position(|(_, c)| c == 'S') + .unwrap(); + + let start: (isize, isize) = ((start % width) as isize, (start / width) as isize); + + let rocks: HashSet<_> = input + .chars() + .enumerate() + .filter(|(_, c)| *c == '#') + .map(|(i, _)| i) + .collect(); + + let rocks: HashSet<(isize, isize)> = rocks + .iter() + .map(|i| ((i % width) as isize, (i / width) as isize)) + .map(|p| (p.0 - start.0, p.1 - start.1)) + .collect(); + + (Garden { rocks }, (width as isize, height as isize)) +} + + +fn generate_neighbors(position: (isize, isize)) -> Vec<(isize, isize)> { + vec![ + (position.0 - 1, position.1), + (position.0 + 1, position.1), + (position.0, position.1 - 1), + (position.0, position.1 + 1), + ] +} + +fn solve_steps(garden: Garden, num_steps: isize) -> usize { + let mut queue = vec![((0, 0), 0)]; // position, number of steps so far + let mut visited = HashSet::new(); + + while let Some(state) = queue.pop() { + if !visited.contains(&state) { + if state.1 < num_steps { + let neighbors = generate_neighbors(state.0); + + for neighbor in neighbors { + if !garden.rocks.contains(&neighbor) { + queue.push((neighbor, state.1 + 1)); + } + } + } + + visited.insert(state); + } + } + + visited + .iter() + .filter_map(|(p, c)| (*c == num_steps).then_some(p)) + .collect::>() + .len() +} + +pub fn part1(input: &'static str) -> String { + let garden = parse(input); + + solve_steps(garden.0, 64).to_string() +} + + +pub fn part2(input: &'static str) -> String { + let (garden, (width, height)) = parse(input); + + let mut queue = VecDeque::new(); + queue.push_back(((0, 0), 0)); + + let mut visited = HashMap::new(); + + while let Some((coords, dist)) = queue.pop_front() { + if let Vacant(e) = visited.entry(coords) { + let neighbors = generate_neighbors(coords); + + for neighbor in neighbors { + if !garden.rocks.contains(&neighbor) + && neighbor.0 >= -width / 2 + && neighbor.1 >= -height / 2 + && neighbor.0 <= width / 2 + && neighbor.1 <= height / 2 + { + queue.push_back((neighbor, dist + 1)); + } + } + + e.insert(dist); + } + } + + let visited_even_outside = visited + .values() + .filter(|v| **v % 2 == 0 && **v > 65) + .count(); + + let visited_even = visited.values().filter(|v| **v % 2 == 1).count(); + + let visited_odd_outside = visited + .values() + .filter(|v| **v % 2 == 1 && **v > 65) + .count(); + + let visited_odd = visited.values().filter(|v| **v % 2 == 0).count(); + + let n = 26501365 / width as usize; + + let visited_inside = visited_even * (n + 1) * (n + 1) + visited_odd * n * n; + let visited_outside = n * visited_even_outside - (n + 1) * visited_odd_outside; + + (visited_inside + visited_outside).to_string() +} + + +lazy_static! { + static ref INPUT: &'static str = { + include_str!("../../../input/21.txt").trim() + }; +} + +pub fn main() { + println!("Part 1 : {}", part1(&INPUT).to_string()); + println!("Part 2 : {}", part2(&INPUT).to_string()); +} \ No newline at end of file diff --git a/code/rs/src/d22.rs b/code/rs/src/d22.rs new file mode 100644 index 0000000..81739b3 --- /dev/null +++ b/code/rs/src/d22.rs @@ -0,0 +1,221 @@ +#![allow(clippy::must_use_candidate, clippy::missing_panics_doc, clippy::identity_op)] +use std::{ + collections::{BTreeMap, BTreeSet, VecDeque}, + fmt::{Debug, Display}, +}; + +use nom::{ + bytes::complete::tag, + character::complete::{newline, u32}, + combinator::map, + multi::separated_list1, + sequence::{separated_pair, terminated, tuple}, + IResult, +}; + +type SupportMap = BTreeMap>; +type Input = (SupportMap, SupportMap); + +fn parse(input: &str) -> Input { + let mut idx = 0usize; + let triple = |s| { + map( + tuple((terminated(u32, tag(",")), terminated(u32, tag(",")), u32)), + |(x, y, z)| Triple::new(z, y, x), + )(s) + }; + let result: IResult<&str, Input> = map( + separated_list1( + newline, + map(separated_pair(triple, tag("~"), triple), |(start, end)| { + idx += 1; + Brick::new(idx, start, end) + }), + ), + |x| settle(&x), + )(input); + + result.unwrap().1 +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Triple { + z: u32, + y: u32, + x: u32, +} + +impl Triple { + fn new(z: u32, y: u32, x: u32) -> Self { + Self { z, y, x } + } + + fn below(&self) -> Self { + Self { + z: self.z - 1, + y: self.y, + x: self.x, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct Brick { + id: usize, + min: Triple, + max: Triple, + cubes: BTreeSet, +} + +impl Brick { + fn new(id: usize, start: Triple, end: Triple) -> Self { + let min = start.min(end); + let max = start.max(end); + let cubes = Self::cubes(min, max); + Self { + id, + min, + max, + cubes, + } + } + + fn on_ground(&self) -> bool { + self.min.z == 1 + } + + fn move_down(&mut self) { + self.min.z -= 1; + self.max.z -= 1; + // this is probably inefficient? + self.cubes = Self::cubes(self.min, self.max); + } + + // generate all the cubes for this brick + fn cubes(min: Triple, max: Triple) -> BTreeSet { + (min.z..=max.z) + .flat_map(|z| { + (min.y..=max.y) + .flat_map(move |y| (min.x..=max.x).map(move |x| Triple::new(z, y, x))) + }) + .collect() + } +} + +impl Display for Brick { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}: {},{},{}~{},{},{}", + self.id, self.min.z, self.min.y, self.min.x, self.max.z, self.max.y, self.max.x + ) + } +} + +impl PartialOrd for Brick { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Brick { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.min.cmp(&other.min).then(self.max.cmp(&other.max)) + } +} + +fn settle(input: &[Brick]) -> Input { + // sort the bricks by z index + let mut bricks = input.to_vec(); + bricks.sort(); + + // The 3D tower, with brick id being the value + let mut settled: BTreeMap = BTreeMap::new(); + + // the "graph" indicating which bricks support other bricks + let mut holding_up: SupportMap = BTreeMap::new(); + let mut sitting_on: SupportMap = BTreeMap::new(); + + for brick in bricks.iter_mut() { + // set up our graph from here + holding_up.insert(brick.id, BTreeSet::new()); + sitting_on.insert(brick.id, BTreeSet::new()); + + // move down until we find something below us + let bricks_below = loop { + // if we hit the ground, return nothing + if brick.on_ground() { + break vec![]; + } + + let bricks_below: Vec = brick + .cubes + .iter() + .filter_map(|c| settled.get(&c.below())) + .cloned() + .collect(); + + // if there are bricks below us, return their IDs + if !bricks_below.is_empty() { + break bricks_below; + } + + brick.move_down(); + }; + + // now that we dropped all the way, set the id where we landed + settled.extend(brick.cubes.iter().map(|c| (*c, brick.id))); + + // set up the graph for each of the bricks + for below in bricks_below { + holding_up.entry(below).or_default().insert(brick.id); + sitting_on.entry(brick.id).or_default().insert(below); + } + } + + (holding_up, sitting_on) +} + +fn part1(input: &Input) -> usize { + let (holding_up, sitting_on) = input; + + holding_up + .iter() + .filter(|(_brick, above)| above.iter().all(|a| sitting_on[&a].len() != 1)) + .count() +} + +fn part2(input: &Input) -> usize { + let (holding_up, sitting_on) = input; + holding_up + .iter() + .map(|(brick, above)| { + // start considering everything above us + let mut queue: VecDeque = VecDeque::from_iter(above.iter().cloned()); + let mut unsupported: BTreeSet = BTreeSet::from_iter(vec![*brick]); + + while let Some(brick) = queue.pop_front() { + // if all of the bricks that we're sitting on are unsupported + if sitting_on[&brick].is_subset(&unsupported) { + // this one is unsupported, and consider the rest of the ones sitting on us + unsupported.insert(brick); + for b in &holding_up[&brick] { + queue.push_back(*b); + } + } + } + + // discount the original brick we put in the falling set to get the rest of it to work + unsupported.len() - 1 + }) + .sum() +} + + +pub fn main() { + let input = include_str!("../../../input/22.txt"); + let input = parse(input); + + println!("Part 1 : {}", part1(&input)); + println!("Part 2 : {}", part2(&input)); +} \ No newline at end of file diff --git a/code/rs/src/lib.rs b/code/rs/src/lib.rs index 6ac006f..e65aeb8 100644 --- a/code/rs/src/lib.rs +++ b/code/rs/src/lib.rs @@ -19,4 +19,6 @@ pub mod d16; pub mod d17; pub mod d18; pub mod d19; -pub mod d20; \ No newline at end of file +pub mod d20; +pub mod d21; +pub mod d22; \ No newline at end of file diff --git a/code/rs/src/main.rs b/code/rs/src/main.rs index 85d6663..45f82c5 100644 --- a/code/rs/src/main.rs +++ b/code/rs/src/main.rs @@ -25,6 +25,8 @@ fn main() { d18::main, d19::main, d20::main, + d21::main, + d22::main, ]; let now = std::time::Instant::now();