From 551a322cc25c0a7d240c2803338011b487ad7400 Mon Sep 17 00:00:00 2001 From: Bram Date: Fri, 13 Dec 2024 15:43:51 +0000 Subject: [PATCH] Add diagnostics + optimizations --- Cargo.toml | 1 + src/day_01/mod.rs | 34 ++++++++++++- src/day_02/mod.rs | 49 ++++++++++++++++++- src/day_03/mod.rs | 29 ++++++++++- src/lib.rs | 47 ++++++++---------- src/main.rs | 14 ++++-- src/utils/diagnostics.rs | 102 +++++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 6 +++ 8 files changed, 245 insertions(+), 37 deletions(-) create mode 100644 src/utils/diagnostics.rs diff --git a/Cargo.toml b/Cargo.toml index 54fddd0..a99a225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +colored="2.1.0" regex="1.11.1" diff --git a/src/day_01/mod.rs b/src/day_01/mod.rs index ec839a1..aaebb34 100644 --- a/src/day_01/mod.rs +++ b/src/day_01/mod.rs @@ -1,2 +1,32 @@ -pub mod part_1; -pub mod part_2; \ No newline at end of file +use crate::utils; + +fn abs_diff(l : &u32, r : &u32) -> u32 { + if l >= r { l - r } else { r - l } +} + +pub fn answer(text : String) -> ( u32, u32 ) { + let ( mut left, mut right ) = from_string(text); + + left.sort_unstable(); + right.sort_unstable(); + + ( left.iter() + .zip(right.iter()) + .map(|(x, y)| abs_diff(&x, &y)) + .sum() + // Part 2 can probably be optimized further but it might not matter + , left.iter() + .map(|x| right.iter().filter(|y| x == *y).count() as u32 * x) + .sum() + ) +} + +fn from_string(s : String) -> ( Vec, Vec ) { + s.split("\n").filter_map(parse_line).unzip() +} + +fn parse_line(s : &str) -> Option<( u32, u32 )> { + let (left, right) = s.trim().split_once(" ")?; + + Some(( utils::str_to_u32(left)?, utils::str_to_u32(right)? )) +} diff --git a/src/day_02/mod.rs b/src/day_02/mod.rs index ec839a1..1c0d329 100644 --- a/src/day_02/mod.rs +++ b/src/day_02/mod.rs @@ -1,2 +1,47 @@ -pub mod part_1; -pub mod part_2; \ No newline at end of file +use crate::utils; + +pub fn answer(text : String) -> ( usize, usize ) { + let (safe, danger) : (Vec<_>, Vec<_>) = text + .split("\n") + .map(parse_line) + .partition(is_safe_line); + + let total_safe = safe.len(); + let total_damp : usize = danger.iter().filter(is_dampened_safe_line).count(); + + ( total_safe, total_safe + total_damp ) +} + +fn is_dampened_safe_line(v : &&Vec) -> bool { + // if is_safe_line(v) { + // return true; + // } + + for i in 0..v.len() { + let mut cv = v.to_vec(); + cv.remove(i); + + if is_safe_line(&cv) { + return true; + } + } + + false +} + +fn is_safe_line(v : &Vec) -> bool { + let diff : Vec = v + .windows(2) + .filter_map(|v| Some(*v.get(0)? as i8 - *v.get(1)? as i8)) + .collect(); + + let all_increasing : bool = diff.iter().all(|&x| x < 0); + let all_decreasing : bool = diff.iter().all(|&x| x > 0); + let all_bounded_ok : bool = diff.iter().all(|&x| x.abs() <= 3); + + all_bounded_ok && (all_increasing || all_decreasing) +} + +fn parse_line(s : &str) -> Vec { + s.split(" ").filter_map(utils::str_to_u8).collect() +} diff --git a/src/day_03/mod.rs b/src/day_03/mod.rs index ec839a1..7538251 100644 --- a/src/day_03/mod.rs +++ b/src/day_03/mod.rs @@ -1,2 +1,27 @@ -pub mod part_1; -pub mod part_2; \ No newline at end of file +use crate::utils; +use regex::Regex; + +pub fn answer(text : String) -> ( u32, u32 ) { + let re : Regex = Regex::new(r"mul\((\d{1,3}),(\d{1,3})\)").unwrap(); + + let (always, ignore) : (Vec<_>, Vec<_>) = text + .split("do()") + .map(|s| s.split_once("don't()").unwrap_or((s, ""))) + .map(|(a, i)| ( mulsum(&re, a), mulsum(&re, i) )) + .unzip(); + + let a : u32 = always.iter().sum(); + let i : u32 = ignore.iter().sum(); + + ( a + i, a ) +} + +fn mulsum(regx : &Regex, s : &str) -> u32 { + regx.captures_iter(s) + .map(|c| c.extract()) + .filter_map(|(_, [l1, l2])| { + Some((utils::str_to_u32(l1)?, utils::str_to_u32(l2)?)) + }) + .map(|(x, y)| x * y) + .sum() +} diff --git a/src/lib.rs b/src/lib.rs index 597a132..5dd1fec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,36 +1,31 @@ -mod utils; mod day_01; mod day_02; mod day_03; -mod day_04; -mod day_05; +mod utils; -pub fn day_01() { - let s : String = utils::read_from_file("inputs/01.txt"); - println!("Day 01 part 1: {}", day_01::part_1::answer(s.as_str())); - println!("Day 01 part 2: {}", day_01::part_2::answer(s.as_str())); +use std::time::Duration; +use crate::utils::read_from_file; +use crate::utils::diagnostics; + +type DailyOutput = ( u128, u128, Duration ); + +pub fn day_01() -> DailyOutput { + let s : String = read_from_file("inputs/01.txt"); + let ( p1, p2, d ) = diagnostics::benchmark(s, day_01::answer); + + ( p1 as u128, p2 as u128, d ) } -pub fn day_02() { - let s : String = utils::read_from_file("inputs/02.txt"); - println!("Day 02 part 1: {}", day_02::part_1::answer(s.as_str())); - println!("Day 02 part 2: {}", day_02::part_2::answer(s.as_str())); +pub fn day_02() -> DailyOutput { + let s : String = read_from_file("inputs/02.txt"); + let (p1, p2, d) = diagnostics::benchmark(s, day_02::answer); + + ( p1 as u128, p2 as u128, d ) } -pub fn day_03() { - let s : String = utils::read_from_file("inputs/03.txt"); - println!("Day 03 part 1: {}", day_03::part_1::answer(s.as_str())); - println!("Day 03 part 2: {}", day_03::part_2::answer(s.as_str())); -} +pub fn day_03() -> DailyOutput { + let s : String = read_from_file("inputs/03.txt"); + let (p1, p2, d) = diagnostics::benchmark(s, day_03::answer); -pub fn day_04() { - let s : String = utils::read_from_file("inputs/04.txt"); - println!("Day 04 part 1: {}", day_04::part_1::answer(s.as_str())); - println!("Day 04 part 2: {}", day_04::part_2::answer(s.as_str())); -} - -pub fn day_05() { - let s : String = utils::read_from_file("inputs/05.txt"); - println!("Day 05 part 1: {}", day_05::part_1::answer(s.as_str())); - println!("Day 05 part 2: {}", day_05::part_2::answer(s.as_str())); + ( p1 as u128, p2 as u128, d ) } diff --git a/src/main.rs b/src/main.rs index 414705c..1905c1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ +mod utils; + fn main() { - aoc_2024::day_01(); - aoc_2024::day_02(); - aoc_2024::day_03(); - aoc_2024::day_04(); - aoc_2024::day_05(); + let mut aoc = crate::utils::diagnostics::AdventOfCode::new(); + + aoc.insert(1, aoc_2024::day_01()); + aoc.insert(2, aoc_2024::day_02()); + aoc.insert(3, aoc_2024::day_03()); + + aoc.show_diagnostics(); } diff --git a/src/utils/diagnostics.rs b/src/utils/diagnostics.rs new file mode 100644 index 0000000..166e67d --- /dev/null +++ b/src/utils/diagnostics.rs @@ -0,0 +1,102 @@ +use colored::Colorize; +use std::time::{Duration, Instant}; + +const MAX_MS_ON_SCREEN : u128 = 100; +const BAR_WIDTH_UNIT : f32 = 10.0; + +pub struct AdventOfCode { + days : [ Option; 25 ], +} +impl AdventOfCode { + pub fn insert(&mut self, i : usize, (p1, p2, d ) : ( u128, u128, Duration )) { + self.days[i - 1] = Some( + DayResults{ part_1 : p1, part_2 : p2, duration : d } + ); + } + + pub fn new() -> AdventOfCode { + AdventOfCode{ + // If I don't do it like this, the compiler wants me to define + // the Copy trait for the DayResults struct. + days : [ + None, None, None, None, None, + None, None, None, None, None, + None, None, None, None, None, + None, None, None, None, None, + None, None, None, None, None, + ] + } + } + + pub fn show_diagnostics(&self) { + println!("| | {: ^12} | {: ^12} | Duration |", + "Part 1", "Part 2" + ); + + let total : u128 = self.days + .iter() + .enumerate() + .map(|(day, result)| { + match result { + Some(r) => { + println!("{}", r.to_string(day, MAX_MS_ON_SCREEN)); + r.duration.as_millis() + }, + None => { + println!("{}", empty_diagnostic(day)); + 0 + }, + } + }) + .sum(); + + println!(""); + println!("Total duration: {total} ms"); + } +} + +struct DayResults { + part_1 : u128, + part_2 : u128, + duration : Duration, +} +impl DayResults { + fn to_string(&self, day : usize, max_duration : u128) -> String { + let duration = self.duration.as_millis(); + let fraction = duration as f32 / max_duration as f32; + + let g = horizontal_bar('#', ' ', 2.0 * BAR_WIDTH_UNIT * fraction, 2.0 * BAR_WIDTH_UNIT); + let o = horizontal_bar('#', ' ', BAR_WIDTH_UNIT * (fraction - 0.50), BAR_WIDTH_UNIT); + let r = horizontal_bar('#', ' ', BAR_WIDTH_UNIT * (fraction - 0.75), BAR_WIDTH_UNIT); + + format!( + "| Day {: >2} | {: >12} | {: >12} | {: >6}ms | [{}{}{}", + day, self.part_1, self.part_2, self.duration.as_millis(), + g.as_str().green(), o.as_str().yellow(), r.as_str().red() + ) + } +} + +pub fn benchmark(s : String, f : fn(String) -> ( A, B )) -> ( A, B, Duration ) { + let now = Instant::now(); + + let ( a, b ) = f(s); + + ( a, b, now.elapsed() ) +} + +fn empty_diagnostic(day : usize) -> String { + format!("| Day {: >2} | {:->12} | {:->12} | {:->8} | [", day, "", "", "") +} + +fn horizontal_bar(full : char, empty : char, width : f32, max_width : f32) -> String { + (0..(max_width.floor() as u32)) + .map(|w| { + if (w as f32) < width { + full + } else { + empty + } + }) + .collect() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 42b5efa..f81d6e2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,7 @@ use std::fs; +pub mod diagnostics; + pub fn read_from_file(name : &str) -> String { match fs::read_to_string(name) { Err(error) => { @@ -13,6 +15,10 @@ pub fn read_from_file(name : &str) -> String { pub fn str_to_i32(s : &str) -> Option { s.trim().to_string().parse::().ok() } + +pub fn str_to_u8(s : &str) -> Option { + s.trim().to_string().parse::().ok() +} pub fn str_to_u32(s : &str) -> Option { s.trim().to_string().parse::().ok()