From bd64157702e3cddfafae32c563450dabf9f4dfaa Mon Sep 17 00:00:00 2001 From: Ty Date: Sun, 3 Dec 2023 00:27:13 -0700 Subject: [PATCH] Day 3 + I formatted everything by accident lol --- src/day1/mod.rs | 48 +++++++++------ src/day2/mod.rs | 81 +++++++++++++------------ src/day3/mod.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 51 ++++++++++------ src/utils.rs | 8 +-- 5 files changed, 268 insertions(+), 75 deletions(-) create mode 100644 src/day3/mod.rs diff --git a/src/day1/mod.rs b/src/day1/mod.rs index 2003b32..03db272 100644 --- a/src/day1/mod.rs +++ b/src/day1/mod.rs @@ -2,28 +2,36 @@ use crate::utils::Day; #[derive(Debug, Default)] pub struct Day1 { - pub input: String + pub input: String, } impl Day for Day1 { fn part1(&mut self) -> String { let mut values = Vec::::new(); - + for line in self.input.lines() { - let digits = line.chars().filter(|char| char.is_numeric()).collect::>(); - let calibrartion_value = format!("{}{}", digits.first().unwrap(), digits.last().unwrap()); + let digits = line + .chars() + .filter(|char| char.is_numeric()) + .collect::>(); + let calibrartion_value = + format!("{}{}", digits.first().unwrap(), digits.last().unwrap()); values.push(calibrartion_value.parse().unwrap()); } - - return values.into_iter().reduce(|acc, e| acc + e).unwrap().to_string(); + + return values + .into_iter() + .reduce(|acc, e| acc + e) + .unwrap() + .to_string(); } - + fn part2(&mut self) -> String { let mut values = Vec::::new(); - + for line in self.input.lines() { let line = line.to_owned(); - + // Find instances of number words and choose the closest, recursively replacing (Can't just .replace b/c of shit like "twone") let digits = vec![ line.match_indices("one").collect::>(), @@ -44,8 +52,9 @@ impl Day for Day1 { line.match_indices("7").collect::>(), line.match_indices("8").collect::>(), line.match_indices("9").collect::>(), - ].into_iter(); - + ] + .into_iter(); + let mut flat = Vec::<(usize, String)>::new(); for digit in digits { for (index, text) in digit { @@ -60,16 +69,21 @@ impl Day for Day1 { .replace("seven", "7") .replace("eight", "8") .replace("nine", "9"); - + flat.push((index, text)); } } flat.sort_by(|a, b| a.0.cmp(&b.0)); - - let calibration_value = format!("{}{}", flat.first().unwrap().1, flat.last().unwrap().1); + + let calibration_value = + format!("{}{}", flat.first().unwrap().1, flat.last().unwrap().1); values.push(calibration_value.parse().unwrap()); } - - return values.into_iter().reduce(|acc, e| acc + e).unwrap().to_string(); + + return values + .into_iter() + .reduce(|acc, e| acc + e) + .unwrap() + .to_string(); } -} \ No newline at end of file +} diff --git a/src/day2/mod.rs b/src/day2/mod.rs index 59c4225..b3b1cbd 100644 --- a/src/day2/mod.rs +++ b/src/day2/mod.rs @@ -5,26 +5,26 @@ use crate::utils::Day; #[derive(Debug, Default)] pub struct Day2 { pub input: String, - pub games: Vec + pub games: Vec, } #[derive(Debug)] pub struct Game { id: usize, - picks: Vec + picks: Vec, } #[derive(Debug)] struct CubeCombo { red: usize, blue: usize, - green: usize + green: usize, } impl Day for Day2 { fn parse(&mut self) { self.games.clear(); - + let game_re = Regex::new(r"Game (\d+): (.+)").unwrap(); let pick_re = Regex::new(r"(?\d+ (?:red|green|blue))(?:, (?\d+ (?:red|green|blue)))?(?:, (?\d+ (?:red|green|blue)))?").unwrap(); @@ -33,7 +33,7 @@ impl Day for Day2 { let captures = game_re.captures(&line).unwrap(); let mut game = Game { id: captures.get(1).unwrap().as_str().parse().unwrap(), - picks: vec![] + picks: vec![], }; let picks = captures.get(2).unwrap().as_str().split("; "); for pick in picks { @@ -46,19 +46,19 @@ impl Day for Day2 { let mut pick = CubeCombo { red: 0, blue: 0, - green: 0 + green: 0, }; - + for capture in captures.iter().filter(|e| e.is_some()).map(|e| e.unwrap()) { let split = capture.as_str().split(" ").collect::>(); match split[1] { "red" => pick.red = split[0].parse().unwrap(), "blue" => pick.blue = split[0].parse().unwrap(), "green" => pick.green = split[0].parse().unwrap(), - _ => panic!("impossible wtf") + _ => panic!("impossible wtf"), } } - + game.picks.push(pick); } @@ -68,40 +68,47 @@ impl Day for Day2 { fn part1(&mut self) -> String { // Sum the illegal game IDs - let sum = self.games + let sum = self + .games .iter() - .filter(|g| - g.picks.iter().all(|pick| pick.red <= 12 && pick.blue <= 14 && pick.green <= 13 ) - ) + .filter(|g| { + g.picks + .iter() + .all(|pick| pick.red <= 12 && pick.blue <= 14 && pick.green <= 13) + }) .fold(0, |acc, e| acc + e.id); - + sum.to_string() } - + fn part2(&mut self) -> String { // Calculate the least amount of dice possible for all games - let sum = self.games.iter().map(|game| { - let mut combo = CubeCombo { - red: 0, - green: 0, - blue: 0 - }; - - for pick in &game.picks { - if combo.red < pick.red { - combo.red = pick.red; + let sum = self + .games + .iter() + .map(|game| { + let mut combo = CubeCombo { + red: 0, + green: 0, + blue: 0, + }; + + for pick in &game.picks { + if combo.red < pick.red { + combo.red = pick.red; + } + if combo.green < pick.green { + combo.green = pick.green; + } + if combo.blue < pick.blue { + combo.blue = pick.blue; + } } - if combo.green < pick.green { - combo.green = pick.green; - } - if combo.blue < pick.blue { - combo.blue = pick.blue; - } - } - - combo - }).fold(0, |acc, combo| acc + (combo.red * combo.blue * combo.green)); - + + combo + }) + .fold(0, |acc, combo| acc + (combo.red * combo.blue * combo.green)); + sum.to_string() } -} \ No newline at end of file +} diff --git a/src/day3/mod.rs b/src/day3/mod.rs new file mode 100644 index 0000000..1647f42 --- /dev/null +++ b/src/day3/mod.rs @@ -0,0 +1,155 @@ +use std::collections::HashMap; + +use regex::Regex; + +use crate::utils::Day; + +#[derive(Debug)] +pub struct Day3 { + pub input: String, +} + +impl Day for Day3 { + fn part1(&mut self) -> String { + let number_re = Regex::new(r"\d+").unwrap(); + let line_length = self.input.split('\n').next().unwrap().len() + 1; + let mut valid = Vec::::new(); + + for number in number_re.find_iter(&self.input) { + let mut checks = vec![]; + if number.start() % line_length != 0 { + checks.push(number.start() - 1) + } + checks.push(number.end()); + checks.append( + &mut ((number.start().checked_sub(1).unwrap_or(number.start()))..=(number.end())) + .flat_map(|i| { + let mut checks = vec![]; + if number.start() / line_length != 0 { + checks.push(i - line_length) + } + if number.start() / line_length != self.input.len() / line_length { + checks.push(i + line_length) + } + checks + }) + .collect::>(), + ); + + for check in checks { + if self + .input + .chars() + .nth(check) + .is_some_and(|character| character != '.' && character.is_ascii_punctuation()) + { + valid.push(number.as_str().parse().unwrap()); + break; + } + } + } + + valid.iter().sum::().to_string() + } + + fn part2(&mut self) -> String { + let number_re = Regex::new(r"\d+").unwrap(); + let line_length = self.input.split('\n').next().unwrap().len() + 1; + let mut valid /* (number, gear_index) */ = Vec::<(usize, usize)>::new(); + + for number in number_re.find_iter(&self.input) { + let mut checks = vec![]; + if number.start() % line_length != 0 { + checks.push(number.start() - 1) + } + checks.push(number.end()); + checks.append( + &mut ((number.start().checked_sub(1).unwrap_or(number.start()))..=(number.end())) + .flat_map(|i| { + let mut checks = vec![]; + if number.start() / line_length != 0 { + checks.push(i - line_length) + } + if number.start() / line_length != self.input.len() / line_length { + checks.push(i + line_length) + } + checks + }) + .collect::>(), + ); + + for check in checks { + if self + .input + .chars() + .nth(check) + .is_some_and(|character| character == '*') + { + valid.push((number.as_str().parse().unwrap(), check)); + break; + } + } + } + + let mut gear_map = HashMap::>::new(); + for (number, gear_index) in valid { + match gear_map.get_mut(&gear_index) { + Some(value) => value.push(number), + None => { + gear_map.insert(gear_index, vec![number]); + } + } + } + + gear_map + .iter() + .map(|(_, numbers)| match numbers.len() { + 2 => numbers[0] * numbers[1], + _ => 0, + }) + .sum::() + .to_string() + } +} + +#[cfg(test)] +mod tests { + use crate::{day3::Day3, utils::Day}; + + #[test] + fn passes_example_1() { + dbg!(Day3 { + input: r#"467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.."# + .to_string(), + } + .solve(1)); + } + + #[test] + fn passes_example_2() { + dbg!(Day3 { + input: r#"467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. +"# + .to_string(), + } + .solve(2)); + } +} diff --git a/src/main.rs b/src/main.rs index 66d3c8e..8b73862 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,16 @@ -use std::{io::Write, time::{Duration, Instant}}; +use std::{ + io::Write, + time::{Duration, Instant}, +}; use clap::{Parser, Subcommand}; -use crate::{day1::Day1, day2::Day2, utils::Day}; +use crate::{day1::Day1, day2::Day2, day3::Day3, utils::Day}; -pub mod utils; pub mod day1; pub mod day2; +pub mod day3; +pub mod utils; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -16,7 +20,7 @@ struct Cli { auth: String, /// The subcommand to run #[command(subcommand)] - subcommand: Subcommands + subcommand: Subcommands, } #[derive(Subcommand)] @@ -26,7 +30,7 @@ enum Subcommands { /// The day to solve for day: u8, /// The part to solve for - part: u8 + part: u8, }, /// Runs solution N amount of times, averaging speed Benchmark { @@ -37,13 +41,13 @@ enum Subcommands { /// The amount of times to run before actually keeping track warmup: u32, /// The amount of times to run - n: u32 + n: u32, }, /// Fetches and prints the given input for a specified day Input { /// The day to fetch - day: u8 - } + day: u8, + }, } #[tokio::main] @@ -67,16 +71,25 @@ async fn main() { let mut day: Box = match day { 1 => Box::new(Day1 { input: response }), - 2 => Box::new(Day2 { input: response, games: vec![] }), - _ => panic!("Invalid day or part #") + 2 => Box::new(Day2 { + input: response, + games: vec![], + }), + 3 => Box::new(Day3 { input: response }), + _ => panic!("Invalid day #"), }; let start = std::time::Instant::now(); let result = day.solve(part); let end = std::time::Instant::now(); println!("Solution: {result}"); println!("Solved in {}ns", (end - start).as_nanos() as f64 / 1000.0); - }, - Subcommands::Benchmark { day, part, warmup, n } => { + } + Subcommands::Benchmark { + day, + part, + warmup, + n, + } => { let response = client .get(format!("https://adventofcode.com/2023/day/{day}/input")) .header("Cookie", format!("session={auth}", auth = cli.auth)) @@ -91,8 +104,12 @@ async fn main() { let mut day: Box = match day { 1 => Box::new(Day1 { input: response }), - 2 => Box::new(Day2 { input: response, games: vec![] }), - _ => panic!("Invalid day #") + 2 => Box::new(Day2 { + input: response, + games: vec![], + }), + 3 => Box::new(Day3 { input: response }), + _ => panic!("Invalid day #"), }; let mut timings = Vec::::new(); for _ in 1..warmup { @@ -104,14 +121,14 @@ async fn main() { day.solve(part); let end = Instant::now(); timings.push(end - start); - if i % (n / 100) == 0 { + if i % (n / 100) == 0 { print!("\r{}%", ((i as f32 / n as f32) * 100.0).round()); let _ = std::io::stdout().flush(); } } let avg = timings.into_iter().sum::() / n; println!("\nAverage timing: {}µs", avg.as_nanos() as f64 / 1000.0); - }, + } Subcommands::Input { day } => { let response = client .get(format!("https://adventofcode.com/2023/day/{day}/input")) @@ -126,6 +143,6 @@ async fn main() { .to_owned(); println!("{response}"); - }, + } } } diff --git a/src/utils.rs b/src/utils.rs index 396a80c..57a7f97 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,13 +1,13 @@ pub trait Day: std::fmt::Debug { fn part1(&mut self) -> String; fn part2(&mut self) -> String; - fn parse(&mut self) { } - fn solve(&mut self, part: u8) -> String { + fn parse(&mut self) {} + fn solve(&mut self, part: u8) -> String { self.parse(); match part { 1 => self.part1(), 2 => self.part2(), - _ => panic!("Invalid part #") + _ => panic!("Invalid part #"), } } -} \ No newline at end of file +}