diff --git a/src/day1/mod.rs b/src/day1/mod.rs index 56db338..2003b32 100644 --- a/src/day1/mod.rs +++ b/src/day1/mod.rs @@ -1,66 +1,75 @@ -pub fn part1(input: String) { - let mut values = Vec::::new(); +use crate::utils::Day; - for line in input.lines() { - 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()); - } - - println!("{}", values.into_iter().reduce(|acc, e| acc + e).unwrap()); +#[derive(Debug, Default)] +pub struct Day1 { + pub input: String } -pub fn part2(input: String) { - let mut values = Vec::::new(); - - for line in 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::>(), - line.match_indices("two").collect::>(), - line.match_indices("three").collect::>(), - line.match_indices("four").collect::>(), - line.match_indices("five").collect::>(), - line.match_indices("six").collect::>(), - line.match_indices("seven").collect::>(), - line.match_indices("eight").collect::>(), - line.match_indices("nine").collect::>(), - line.match_indices("1").collect::>(), - line.match_indices("2").collect::>(), - line.match_indices("3").collect::>(), - line.match_indices("4").collect::>(), - line.match_indices("5").collect::>(), - line.match_indices("6").collect::>(), - line.match_indices("7").collect::>(), - line.match_indices("8").collect::>(), - line.match_indices("9").collect::>(), - ].into_iter(); - - let mut flat = Vec::<(usize, String)>::new(); - for digit in digits { - for (index, text) in digit { - let text = text - .to_owned() - .replace("one", "1") - .replace("two", "2") - .replace("three", "3") - .replace("four", "4") - .replace("five", "5") - .replace("six", "6") - .replace("seven", "7") - .replace("eight", "8") - .replace("nine", "9"); - - flat.push((index, text)); - } +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()); + values.push(calibrartion_value.parse().unwrap()); } - flat.sort_by(|a, b| a.0.cmp(&b.0)); - - 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(); + } + + 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::>(), + line.match_indices("two").collect::>(), + line.match_indices("three").collect::>(), + line.match_indices("four").collect::>(), + line.match_indices("five").collect::>(), + line.match_indices("six").collect::>(), + line.match_indices("seven").collect::>(), + line.match_indices("eight").collect::>(), + line.match_indices("nine").collect::>(), + line.match_indices("1").collect::>(), + line.match_indices("2").collect::>(), + line.match_indices("3").collect::>(), + line.match_indices("4").collect::>(), + line.match_indices("5").collect::>(), + line.match_indices("6").collect::>(), + line.match_indices("7").collect::>(), + line.match_indices("8").collect::>(), + line.match_indices("9").collect::>(), + ].into_iter(); + + let mut flat = Vec::<(usize, String)>::new(); + for digit in digits { + for (index, text) in digit { + let text = text + .to_owned() + .replace("one", "1") + .replace("two", "2") + .replace("three", "3") + .replace("four", "4") + .replace("five", "5") + .replace("six", "6") + .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); + values.push(calibration_value.parse().unwrap()); + } + + return values.into_iter().reduce(|acc, e| acc + e).unwrap().to_string(); } - - println!("{}", values.into_iter().reduce(|acc, e| acc + e).unwrap()); } \ No newline at end of file diff --git a/src/day2/mod.rs b/src/day2/mod.rs index 5fd8d1d..59c4225 100644 --- a/src/day2/mod.rs +++ b/src/day2/mod.rs @@ -1,7 +1,15 @@ use regex::Regex; +use crate::utils::Day; + +#[derive(Debug, Default)] +pub struct Day2 { + pub input: String, + pub games: Vec +} + #[derive(Debug)] -struct Game { +pub struct Game { id: usize, picks: Vec } @@ -13,88 +21,87 @@ struct CubeCombo { green: usize } -fn parse_games(input: String) -> Vec { - 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(); - let mut games = Vec::::new(); +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(); - for line in input.lines() { - let line = line.to_string(); - let captures = game_re.captures(&line).unwrap(); - let mut game = Game { - id: captures.get(1).unwrap().as_str().parse().unwrap(), - picks: vec![] - }; - let picks = captures.get(2).unwrap().as_str().split("; "); - for pick in picks { - let captures = pick_re.captures(pick).unwrap(); - let captures = vec![ - captures.name("first"), - captures.name("second"), - captures.name("third"), - ]; - let mut pick = CubeCombo { - red: 0, - blue: 0, - green: 0 + for line in self.input.lines() { + let line = line.to_string(); + let captures = game_re.captures(&line).unwrap(); + let mut game = Game { + id: captures.get(1).unwrap().as_str().parse().unwrap(), + picks: vec![] }; - - 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") + let picks = captures.get(2).unwrap().as_str().split("; "); + for pick in picks { + let captures = pick_re.captures(pick).unwrap(); + let captures = vec![ + captures.name("first"), + captures.name("second"), + captures.name("third"), + ]; + let mut pick = CubeCombo { + red: 0, + blue: 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") + } } + + game.picks.push(pick); } - game.picks.push(pick); + self.games.push(game); } - games.push(game); } - games -} - -pub fn part1(input: String) { - let games = parse_games(input); - - // Sum the illegal game IDs - let sum = games - .iter() - .filter(|g| - g.picks.iter().all(|pick| pick.red <= 12 && pick.blue <= 14 && pick.green <= 13 ) - ) - .fold(0, |acc, e| acc + e.id); - println!("{sum}"); -} - -pub fn part2(input: String) { - let games = parse_games(input); + fn part1(&mut self) -> String { + // Sum the illegal game IDs + let sum = self.games + .iter() + .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() + } - // Calculate the least amount of dice possible for all games - let sum = 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; + 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; + } + 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)); - - println!("{sum}"); + + 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/main.rs b/src/main.rs index c64067c..66d3c8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ +use std::{io::Write, time::{Duration, Instant}}; + use clap::{Parser, Subcommand}; +use crate::{day1::Day1, day2::Day2, utils::Day}; + pub mod utils; pub mod day1; pub mod day2; @@ -24,6 +28,17 @@ enum Subcommands { /// The part to solve for part: u8 }, + /// Runs solution N amount of times, averaging speed + Benchmark { + /// The day to solve for + day: u8, + /// The part to solve for + part: u8, + /// The amount of times to run before actually keeping track + warmup: u32, + /// The amount of times to run + n: u32 + }, /// Fetches and prints the given input for a specified day Input { /// The day to fetch @@ -50,13 +65,52 @@ async fn main() { .trim_end() .to_owned(); - match (day, part) { - (1, 1) => day1::part1(response), - (1, 2) => day1::part2(response), - (2, 1) => day2::part1(response), - (2, 2) => day2::part2(response), + 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 #") + }; + 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 } => { + let response = client + .get(format!("https://adventofcode.com/2023/day/{day}/input")) + .header("Cookie", format!("session={auth}", auth = cli.auth)) + .send() + .await + .unwrap() + .text() + .await + .unwrap() + .trim_end() + .to_owned(); + + let mut day: Box = match day { + 1 => Box::new(Day1 { input: response }), + 2 => Box::new(Day2 { input: response, games: vec![] }), + _ => panic!("Invalid day #") + }; + let mut timings = Vec::::new(); + for _ in 1..warmup { + day.solve(part); } + + for i in 1..=n { + let start = Instant::now(); + day.solve(part); + let end = Instant::now(); + timings.push(end - start); + 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 diff --git a/src/utils.rs b/src/utils.rs index 63af3d9..396a80c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,13 @@ -pub enum CoroutineYield { - Data, - Output(String) -} - -pub enum CoroutineYieldResponse { - Data(String), +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 { + self.parse(); + match part { + 1 => self.part1(), + 2 => self.part2(), + _ => panic!("Invalid part #") + } + } } \ No newline at end of file