diff --git a/2023/Cargo.lock b/2023/Cargo.lock index 118dbe5..b527531 100644 --- a/2023/Cargo.lock +++ b/2023/Cargo.lock @@ -102,6 +102,16 @@ dependencies = [ "rstest", ] +[[package]] +name = "day-11" +version = "2023.0.0" +dependencies = [ + "glam", + "itertools", + "nom", + "nom_locate", +] + [[package]] name = "day-2" version = "2023.0.0" diff --git a/2023/Cargo.toml b/2023/Cargo.toml index 7d806c7..58fbf08 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -18,6 +18,7 @@ nom_locate= "4.2.0" rstest = "0.18.2" rstest_reuse = "0.6.0" dhat = "0.3.2" +glam = "0.24.2" [profile.dhat] diff --git a/2023/day-10/src/part2.rs b/2023/day-10/src/part2.rs index 2f9bf5c..1168eef 100644 --- a/2023/day-10/src/part2.rs +++ b/2023/day-10/src/part2.rs @@ -165,7 +165,6 @@ pub fn part2(input: &str) -> String { }; let mut pieces = HashMap::new(); - //TODO might need to get the actual start type pieces.insert(start_node.position, start_node_true_type); successors( diff --git a/2023/day-11/Cargo.toml b/2023/day-11/Cargo.toml new file mode 100644 index 0000000..5dd44c5 --- /dev/null +++ b/2023/day-11/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "day-11" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = { workspace = true } +itertools = {workspace = true } +glam = {workspace = true} +nom_locate = {workspace = true } diff --git a/2023/day-11/src/lib.rs b/2023/day-11/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-11/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-11/src/main.rs b/2023/day-11/src/main.rs new file mode 100644 index 0000000..4a2069b --- /dev/null +++ b/2023/day-11/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_11::part1; +use day_11::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input, 1_000_000); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-11/src/part1.rs b/2023/day-11/src/part1.rs new file mode 100644 index 0000000..418e55f --- /dev/null +++ b/2023/day-11/src/part1.rs @@ -0,0 +1,95 @@ +#![warn(clippy::all, clippy::pedantic)] + +use glam::IVec2; +use itertools::Itertools; +use std::collections::HashSet; + +#[must_use] +pub fn part1(input: &str) -> String { + let points = parse_input(input); + let ((min_x, min_y), (mut max_x, /*mut*/ max_y)) = points.iter().fold( + ((i32::MAX, i32::MAX), (i32::MIN, i32::MIN)), + |((min_x, min_y), (max_x, max_y)), pos| { + let min_x = min_x.min(pos.x); + let min_y = min_y.min(pos.y); + let max_x = max_x.max(pos.x); + let max_y = max_y.max(pos.y); + ((min_x, min_y), (max_x, max_y)) + }, + ); + let mut modifier = 0; + let mut adjusted_points = HashSet::new(); + for x in min_x..=max_x { + let column = (min_y..=max_y) + .filter_map(|y| points.get(&(x, y).into())) + .collect::>(); + if column.is_empty() { + modifier += 1; + } + for point in column { + adjusted_points.insert(*point + IVec2::new(modifier, 0)); + } + } + max_x += modifier; + + let mut modifier = 0; + let mut points = HashSet::new(); + for y in min_y..=max_y { + let row = (min_x..=max_x) + .filter_map(|x| adjusted_points.get(&(x, y).into())) + .collect::>(); + if row.is_empty() { + modifier += 1; + } + for point in row { + points.insert(*point + IVec2::new(0, modifier)); + } + } + //max_y += modifier; + (points + .iter() + .cartesian_product(points.iter()) + .filter_map(|(a, b)| (a != b).then_some(*a - *b)) + .map(|pos| pos.x.abs() + pos.y.abs()) + .sum::() + / 2) + .to_string() +} + +fn parse_input(input: &str) -> HashSet { + input + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.chars().enumerate().filter_map(move |(x, c)| { + (c != '.').then_some(IVec2::new( + i32::try_from(x).unwrap(), + i32::try_from(y).unwrap(), + )) + }) + }) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#....."; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "374".to_string()); + } +} + diff --git a/2023/day-11/src/part2.rs b/2023/day-11/src/part2.rs new file mode 100644 index 0000000..e2285cf --- /dev/null +++ b/2023/day-11/src/part2.rs @@ -0,0 +1,103 @@ +#![warn(clippy::all, clippy::pedantic)] + +use glam::I64Vec2; +use itertools::Itertools; +use std::collections::HashSet; + +/// day 11 part 2 of aoc 2023 +/// +/// # Arguments +/// - input the input for today's puzzle +/// +/// # Panics +/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than +/// usize +#[must_use] +pub fn part2(input: &str, modr: i64) -> String { + let points = parse_input(input); + let ((min_x, min_y), (mut max_x, /*mut*/ max_y)) = points.iter().fold( + ((i64::MAX, i64::MAX), (i64::MIN, i64::MIN)), + |((min_x, min_y), (max_x, max_y)), pos| { + let min_x = min_x.min(pos.x); + let min_y = min_y.min(pos.y); + let max_x = max_x.max(pos.x); + let max_y = max_y.max(pos.y); + ((min_x, min_y), (max_x, max_y)) + }, + ); + let mut modifier = 0; + let mut adjusted_points = HashSet::new(); + for x in min_x..=max_x { + let column = (min_y..=max_y) + .filter_map(|y| points.get(&(x, y).into())) + .collect::>(); + if column.is_empty() { + modifier += modr-1; + } + for point in column { + adjusted_points.insert(*point + I64Vec2::new(modifier, 0)); + } + } + max_x += modifier; + + let mut modifier = 0; + let mut points = HashSet::new(); + for y in min_y..=max_y { + let row = (min_x..=max_x) + .filter_map(|x| adjusted_points.get(&(x, y).into())) + .collect::>(); + if row.is_empty() { + modifier += modr-1; + } + for point in row { + points.insert(*point + I64Vec2::new(0, modifier)); + } + } + //max_y += modifier; + (points + .iter() + .cartesian_product(points.iter()) + .filter_map(|(a, b)| (*a != *b).then_some(*a-*b)) + .map(|a| u64::try_from(a.x.abs()+a.y.abs()).unwrap()) + .sum::() + / 2) + .to_string() +} + +fn parse_input(input: &str) -> HashSet { + input + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.chars().enumerate().filter_map(move |(x, c)| { + (c != '.').then_some(I64Vec2::new( + i64::try_from(x).unwrap(), + i64::try_from(y).unwrap(), + )) + }) + }) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#....."; + + #[test] + fn part2_works() { + let result = part2(INPUT, 10); + assert_eq!(result, "1030".to_string()); + } +} +