From 4bfd29dcd51e2d24105e3cef43fdfc1e86b34301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Sun, 3 Dec 2023 09:51:45 +0100 Subject: [PATCH] Add day 3 --- 2023/day3/.gitignore | 1 + 2023/day3/Cargo.lock | 16 ++++ 2023/day3/Cargo.toml | 9 ++ 2023/day3/src/main.rs | 205 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 2023/day3/.gitignore create mode 100644 2023/day3/Cargo.lock create mode 100644 2023/day3/Cargo.toml create mode 100644 2023/day3/src/main.rs diff --git a/2023/day3/.gitignore b/2023/day3/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/2023/day3/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/2023/day3/Cargo.lock b/2023/day3/Cargo.lock new file mode 100644 index 0000000..0d04e02 --- /dev/null +++ b/2023/day3/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day3" +version = "0.1.0" +dependencies = [ + "indoc", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" diff --git a/2023/day3/Cargo.toml b/2023/day3/Cargo.toml new file mode 100644 index 0000000..743643d --- /dev/null +++ b/2023/day3/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day3" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indoc = "2.0.4" diff --git a/2023/day3/src/main.rs b/2023/day3/src/main.rs new file mode 100644 index 0000000..d1f0224 --- /dev/null +++ b/2023/day3/src/main.rs @@ -0,0 +1,205 @@ +use std::cmp::min; + +#[derive(Debug)] +struct Point { + x: usize, + y: usize, +} + +#[derive(Debug, Eq, PartialEq)] +struct Number { + line: usize, + range: (usize, usize), +} + +impl Number { + fn value(&self, lines: &[&str]) -> usize { + let mut accum: usize = 0; + for (i, x) in (self.range.0..=self.range.1).rev().enumerate() { + let val = lines[self.line].chars().collect::>()[x] + .to_digit(10) + .unwrap() as usize; + accum += val * 10_usize.pow(i as u32); + } + accum + } + + fn contains(&self, point: &Point) -> bool { + point.y == self.line && point.x >= self.range.0 && point.x <= self.range.1 + } +} + +fn find_numbers(lines: &[&str]) -> Vec { + let mut numbers: Vec = vec![]; + + for (y, line) in lines.iter().enumerate() { + let mut in_number = false; + let mut start = 0; + for (x, c) in line.chars().enumerate() { + if c.is_ascii_digit() { + if !in_number { + start = x; + in_number = true; + } + } else if in_number { + let end = x - 1; + in_number = false; + numbers.push(Number { + line: y, + range: (start, end), + }) + } + } + if in_number { + numbers.push(Number { + line: y, + range: (start, line.len() - 1), + }) + } + } + + numbers +} + +fn part1(input: &str) -> usize { + let lines: Vec<&str> = input.lines().collect(); + let numbers = find_numbers(&lines); + + let mut numbers_with_adjacent_symbol = vec![]; + + for number in &numbers { + let mut has_adjacent_symbol = false; + + let bounding_box: (Point, Point) = ( + Point { + // this would underflow on the first column + x: number.range.0.saturating_sub(1), + // this would underflow on the first line + y: number.line.saturating_sub(1), + }, + Point { + x: min(number.range.1 + 1, lines[number.line].len() - 1), + // this would overflow on the last line + y: min(number.line + 1, lines.len() - 1), + }, + ); + + 'outer: for line in lines + .iter() + .skip(bounding_box.0.y) + .take(bounding_box.1.y + 1) + { + for x in bounding_box.0.x..=bounding_box.1.x { + let c = line.chars().collect::>()[x]; + if !c.is_ascii_digit() && c != '.' { + has_adjacent_symbol = true; + break 'outer; + } + } + } + + if has_adjacent_symbol { + numbers_with_adjacent_symbol.push(number); + } + } + + let sum: usize = numbers_with_adjacent_symbol + .into_iter() + .map(|n| n.value(&lines)) + .sum(); + + sum +} + +fn part2(input: &str) -> usize { + let mut gears: Vec = vec![]; + + let lines: Vec<&str> = input.lines().collect(); + + let numbers = find_numbers(&lines); + + for (y, line) in lines.iter().enumerate() { + for (x, c) in line.chars().enumerate() { + if c == '*' { + gears.push(Point { x, y }); + } + } + } + + let mut gear_ratios: Vec> = vec![]; + for gear in &gears { + let mut matching_numbers: Vec<&Number> = vec![]; + for number in &numbers { + for x in gear.x.saturating_sub(1)..=min(gear.x + 1, lines[0].len()) { + for y in gear.y.saturating_sub(1)..=min(gear.y + 1, lines.len()) { + let point = Point { x, y }; + if number.contains(&point) && !matching_numbers.contains(&number) { + matching_numbers.push(number); + } + } + } + } + if matching_numbers.len() >= 2 { + gear_ratios.push(matching_numbers); + } + } + + gear_ratios + .into_iter() + .map(|gear_numbers| { + gear_numbers + .into_iter() + .map(|n| n.value(&lines)) + .product::() + }) + .sum() +} + +fn main() { + let input = std::fs::read_to_string("./input").unwrap(); + + println!("Part 1 : {}", part1(&input)); + println!("Part 2 : {}", part2(&input)); +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn example_01() { + let input = indoc! {" + 467..114.. + ...*...... + ..35..633. + ......#... + 617*...... + .....+.58. + ..592..... + ......755. + ...$.*.... + .664.598.. + "}; + + assert_eq!(part1(input), 4361); + } + + #[test] + fn example_02() { + let input = indoc! {" + 467..114.. + ...*...... + ..35..633. + ......#... + 617*...... + .....+.58. + ..592..... + ......755. + ...$.*.... + .664.598.. + "}; + + assert_eq!(part2(input), 467835); + } +}