From 452b15fd032c1a9993348b16312966fbd494e8e5 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 8 Dec 2023 17:55:38 +0100 Subject: [PATCH 1/4] add table output format --- src/main.rs | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9e52d02..e932f42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,10 @@ use std::{ fs::File, io::{BufRead, BufReader}, path::Path, + time::Instant, }; +use once_cell::sync::Lazy; use path_macro::path; pub(crate) fn read_input(day: u8) -> Vec { @@ -34,10 +36,18 @@ fn read_input_file>(path: P) -> Vec { input } +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + macro_rules! days { ( $($n:expr, $module:ident,)* ) => { $(mod $module;)* + static MAX_DAY: Lazy = Lazy::new(|| 0$(.max($n))*); + fn get_result(day: u8, part: u8) -> String { let input = read_input(day); @@ -70,19 +80,29 @@ fn main() { let mut args = std::env::args(); args.next(); - let day = args - .next() - .expect("no day") - .parse::() - .expect("invalid day"); + if let Some(day) = args.next().and_then(|a| a.parse::().ok()) { + let part = args + .next() + .and_then(|arg| arg.parse::().ok().map(|p| p.clamp(1, 2))) + .unwrap_or(1); - let part = args - .next() - .and_then(|arg| arg.parse::().ok().map(|p| p.clamp(1, 2))) - .unwrap_or(1); + let res = get_result(day, part); - let res = get_result(day, part); + println!("✨The result for day {}/{:?} is✨", day, part); + println!("{}", res); + } else { + println!("AoC 23 | Part one | Part two"); + println!("=======|======================|======================"); + for day in 1..=*MAX_DAY { + let t = Instant::now(); + let res_a = get_result(day, 1); + let res_a = format!("{res_a} ({}ms)", t.elapsed().as_millis()); + print!("Day {day:2} | {res_a:20}"); - println!("✨The result for day {}/{:?} is✨", day, part); - println!("{}", res); + let t = Instant::now(); + let res_b = get_result(day, 2); + let res_b = format!("{res_b} ({}ms)", t.elapsed().as_millis()); + println!(" | {res_b:20}") + } + } } From fd056ae79e10f65dbac68fd642420522c3ead7e3 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 8 Dec 2023 22:06:18 +0100 Subject: [PATCH 2/4] update days macro --- src/day0.rs | 4 +-- src/day1.rs | 4 +-- src/day2.rs | 4 +-- src/day3.rs | 4 +-- src/day4.rs | 4 +-- src/day5.rs | 4 +-- src/day6.rs | 4 +-- src/day7.rs | 4 +-- src/day8.rs | 4 +-- src/main.rs | 73 +++++++++++++++++++++++++++++------------------------ 10 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/day0.rs b/src/day0.rs index c1b81ce..d21bdcb 100644 --- a/src/day0.rs +++ b/src/day0.rs @@ -1,5 +1,7 @@ //! Dummy challenge for testing +pub const DAY: u8 = 0; + pub fn part1(input: Vec) -> String { input[0].to_owned() } @@ -12,8 +14,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 0; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "123"); diff --git a/src/day1.rs b/src/day1.rs index 739bc93..2f7e9b5 100644 --- a/src/day1.rs +++ b/src/day1.rs @@ -2,6 +2,8 @@ use aho_corasick::AhoCorasick; +pub const DAY: u8 = 1; + pub fn part1(input: Vec) -> String { input .iter() @@ -47,8 +49,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 1; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "142"); diff --git a/src/day2.rs b/src/day2.rs index 3121160..444d953 100644 --- a/src/day2.rs +++ b/src/day2.rs @@ -3,6 +3,8 @@ use once_cell::sync::Lazy; use regex::Regex; +pub const DAY: u8 = 2; + #[derive(Debug)] struct Game { id: u32, @@ -94,8 +96,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 2; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "8"); diff --git a/src/day3.rs b/src/day3.rs index deff3bf..afa6938 100644 --- a/src/day3.rs +++ b/src/day3.rs @@ -6,6 +6,8 @@ use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; +pub const DAY: u8 = 3; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct Pos { x: i64, @@ -181,8 +183,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 3; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "4361"); diff --git a/src/day4.rs b/src/day4.rs index 272fba0..88144c0 100644 --- a/src/day4.rs +++ b/src/day4.rs @@ -4,6 +4,8 @@ use std::collections::HashSet; use itertools::Itertools; +pub const DAY: u8 = 4; + fn parse(line: &str) -> (HashSet, HashSet) { let (_, tmp) = line.split_once(": ").unwrap(); let (win, got) = tmp.split_once(" | ").unwrap(); @@ -62,8 +64,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 4; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "13"); diff --git a/src/day5.rs b/src/day5.rs index 1d1679f..41a03aa 100644 --- a/src/day5.rs +++ b/src/day5.rs @@ -5,6 +5,8 @@ use std::collections::HashMap; use itertools::Itertools; use rangemap::{RangeMap, RangeSet}; +pub const DAY: u8 = 5; + #[derive(Debug)] struct Parsed { seeds: Vec, @@ -103,8 +105,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 5; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "35"); diff --git a/src/day6.rs b/src/day6.rs index add2f59..0724000 100644 --- a/src/day6.rs +++ b/src/day6.rs @@ -4,6 +4,8 @@ use std::ops::RangeInclusive; use itertools::Itertools; +pub const DAY: u8 = 6; + #[derive(Debug)] struct Race { t: u64, @@ -67,8 +69,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 6; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "288"); diff --git a/src/day7.rs b/src/day7.rs index 6d4450c..3a7c7b1 100644 --- a/src/day7.rs +++ b/src/day7.rs @@ -7,6 +7,8 @@ use std::{ use itertools::Itertools; +pub const DAY: u8 = 7; + #[derive(Debug, PartialEq, Eq)] struct Hand(String); @@ -133,8 +135,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 7; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "6440"); diff --git a/src/day8.rs b/src/day8.rs index 1fdacb4..80aa4e4 100644 --- a/src/day8.rs +++ b/src/day8.rs @@ -4,6 +4,8 @@ use std::collections::{HashMap, HashSet}; use itertools::Itertools; +pub const DAY: u8 = 8; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Direction { Right, @@ -90,8 +92,6 @@ pub fn part2(input: Vec) -> String { mod tests { use super::*; - const DAY: u8 = 8; - #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "2"); diff --git a/src/main.rs b/src/main.rs index e932f42..1c2cc10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,49 +36,55 @@ fn read_input_file>(path: P) -> Vec { input } -macro_rules! replace_expr { - ($_t:tt $sub:expr) => { - $sub - }; +fn get_result(day: &Day, part: u8) -> String { + let input = read_input(day.n); + if part == 2 { + (day.p2)(input) + } else { + (day.p1)(input) + } +} + +type SolveFn = Box) -> String>; + +struct Day { + n: u8, + p1: SolveFn, + p2: SolveFn, } macro_rules! days { - ( $($n:expr, $module:ident,)* ) => { + ( $($module:ident,)* ) => { $(mod $module;)* - static MAX_DAY: Lazy = Lazy::new(|| 0$(.max($n))*); - - fn get_result(day: u8, part: u8) -> String { - let input = read_input(day); - - match day { - $( - $n => match part { - 2 => $module::part2(input), - _ => $module::part1(input), - } - )* - _ => panic!("day {} missing", day), - } + fn days() -> Vec { + vec![ + $(Day { + n: $module::DAY, + p1: Box::new($module::part1), + p2: Box::new($module::part2), + },)* + ] } }; } days! { - 0, day0, - 1, day1, - 2, day2, - 3, day3, - 4, day4, - 5, day5, - 6, day6, - 7, day7, - 8, day8, + day0, + day1, + day2, + day3, + day4, + day5, + day6, + day7, + day8, } fn main() { let mut args = std::env::args(); args.next(); + let days = days(); if let Some(day) = args.next().and_then(|a| a.parse::().ok()) { let part = args @@ -86,21 +92,22 @@ fn main() { .and_then(|arg| arg.parse::().ok().map(|p| p.clamp(1, 2))) .unwrap_or(1); - let res = get_result(day, part); + let dobj = days.iter().find(|d| d.n == day).expect("Day not found"); + let res = get_result(dobj, part); println!("✨The result for day {}/{:?} is✨", day, part); println!("{}", res); } else { println!("AoC 23 | Part one | Part two"); println!("=======|======================|======================"); - for day in 1..=*MAX_DAY { + for day in days { let t = Instant::now(); - let res_a = get_result(day, 1); + let res_a = get_result(&day, 1); let res_a = format!("{res_a} ({}ms)", t.elapsed().as_millis()); - print!("Day {day:2} | {res_a:20}"); + print!("Day {:2} | {res_a:20}", day.n); let t = Instant::now(); - let res_b = get_result(day, 2); + let res_b = get_result(&day, 2); let res_b = format!("{res_b} ({}ms)", t.elapsed().as_millis()); println!(" | {res_b:20}") } From 0b2475d2bf675529802596f05b478320bacce151 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 9 Dec 2023 01:43:07 +0100 Subject: [PATCH 3/4] improve day 5 --- src/day5.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/src/day5.rs b/src/day5.rs index 41a03aa..0f08fc0 100644 --- a/src/day5.rs +++ b/src/day5.rs @@ -1,6 +1,6 @@ -//! Day 5 +//! Day 5: If You Give A Seed A Fertilizer -use std::collections::HashMap; +use std::{borrow::BorrowMut, collections::HashMap, ops::Range}; use itertools::Itertools; use rangemap::{RangeMap, RangeSet}; @@ -10,7 +10,7 @@ pub const DAY: u8 = 5; #[derive(Debug)] struct Parsed { seeds: Vec, - maps: Vec>, + maps: Vec>>, } impl Parsed { @@ -18,11 +18,45 @@ impl Parsed { let mut s = seed; for map in &self.maps { if let Some((rng, dst)) = map.get_key_value(&s) { - s = dst + (s - rng.start); + s = dst.start + (s - rng.start); } } s } + + /// Transform the list of seed maps into a single map + fn transform(&self) -> RangeMap> { + self.maps + .clone() + .into_iter() + .reduce(|acc, mut m| { + // have dangling keys from next map as fallback + let mut res = m.clone(); + for (k, v) in acc.iter() { + // lookup value range in next map + for (olk, olv) in m.overlapping(v) { + let corr = olk.start.max(v.start)..olk.end.min(v.end); + let offset = corr.start - v.start; + let voffset = corr.start - olk.start; + let s = k.start + offset; + let l = rlen(&corr); + let olvs = olv.start + voffset; + res.insert(s..(s + l), olvs..(olvs + l)); + } + + // ranges in acc map not covered by next map + for g in m.gaps(v) { + let offset = g.start - v.start; + let s = k.start + offset; + let vs = v.start + offset; + let l = rlen(&g); + res.insert(s..(s + l), vs..(vs + l)); + } + } + res + }) + .unwrap() + } } fn parse(input: Vec) -> Parsed { @@ -58,7 +92,10 @@ fn parse(input: Vec) -> Parsed { .collect_tuple() .unwrap(); - map.insert(mentry.1..(mentry.1 + mentry.2), mentry.0); + map.insert( + mentry.1..(mentry.1 + mentry.2), + mentry.0..(mentry.0 + mentry.2), + ); } if !map.is_empty() { maps.push(map); @@ -69,11 +106,15 @@ fn parse(input: Vec) -> Parsed { pub fn part1(input: Vec) -> String { let parsed = parse(input); + let tf = parsed.transform(); parsed .seeds .iter() - .map(|s| parsed.lookup(*s)) + .map(|s| { + let (rng, dst) = tf.get_key_value(s).unwrap(); + dst.start + (s - rng.start) + }) .min() .unwrap() .to_string() @@ -81,6 +122,7 @@ pub fn part1(input: Vec) -> String { pub fn part2(input: Vec) -> String { let parsed = parse(input); + let tf = parsed.transform(); let ranges = parsed .seeds @@ -89,18 +131,36 @@ pub fn part2(input: Vec) -> String { .map(|(a, b)| (*a)..(*a + *b)) .collect::>(); - ranges - .into_iter() - .flat_map(|r| { - dbg!(&r); - r.into_iter() + tf.into_iter() + .sorted_by_key(|(_, soils)| soils.start) + .find_map(|(seeds, soils)| { + ranges + .iter() + .find_map(|r| lowest_intersection(r, &seeds)) + .map(|seed| soils.start + (seed - seeds.start)) }) - .map(|s| parsed.lookup(s)) - .min() .unwrap() .to_string() } +fn rlen(range: &Range) -> u64 { + range.end - range.start +} + +fn lowest_intersection(r1: &Range, r2: &Range) -> Option { + let (l, h) = if r1.start < r2.start { + (r1, r2) + } else { + (r2, r1) + }; + + if h.start <= l.end { + Some(h.start) + } else { + None + } +} + #[cfg(test)] mod tests { use super::*; From acd627a0084083b05ee3b08eb06d5a5d013549ef Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 9 Dec 2023 01:53:48 +0100 Subject: [PATCH 4/4] feat: add titles for days --- src/day0.rs | 3 ++- src/day1.rs | 3 ++- src/day2.rs | 3 ++- src/day3.rs | 3 ++- src/day4.rs | 3 ++- src/day5.rs | 1 + src/day6.rs | 3 ++- src/day7.rs | 3 ++- src/day8.rs | 3 ++- src/main.rs | 11 +++++++---- 10 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/day0.rs b/src/day0.rs index d21bdcb..77be5c1 100644 --- a/src/day0.rs +++ b/src/day0.rs @@ -1,6 +1,7 @@ -//! Dummy challenge for testing +//! Day 0: Dummy challenge for testing pub const DAY: u8 = 0; +pub const TITLE: &str = "Dummy challenge for testing"; pub fn part1(input: Vec) -> String { input[0].to_owned() diff --git a/src/day1.rs b/src/day1.rs index 2f7e9b5..053ea65 100644 --- a/src/day1.rs +++ b/src/day1.rs @@ -1,8 +1,9 @@ -//! Day 1 +//! Day 1: Trebuchet?! use aho_corasick::AhoCorasick; pub const DAY: u8 = 1; +pub const TITLE: &str = "Trebuchet?!"; pub fn part1(input: Vec) -> String { input diff --git a/src/day2.rs b/src/day2.rs index 444d953..a08eb7c 100644 --- a/src/day2.rs +++ b/src/day2.rs @@ -1,9 +1,10 @@ -//! Day 2 +//! Day 2: Cube Conundrum use once_cell::sync::Lazy; use regex::Regex; pub const DAY: u8 = 2; +pub const TITLE: &str = "Cube Conundrum"; #[derive(Debug)] struct Game { diff --git a/src/day3.rs b/src/day3.rs index afa6938..4e92b38 100644 --- a/src/day3.rs +++ b/src/day3.rs @@ -1,4 +1,4 @@ -//! Day 3 +//! Day 3: Gear Ratios use std::collections::{HashMap, HashSet}; @@ -7,6 +7,7 @@ use once_cell::sync::Lazy; use regex::Regex; pub const DAY: u8 = 3; +pub const TITLE: &str = "Gear Ratios"; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct Pos { diff --git a/src/day4.rs b/src/day4.rs index 88144c0..d66c436 100644 --- a/src/day4.rs +++ b/src/day4.rs @@ -1,10 +1,11 @@ -//! Day 4 +//! Day 4: Scratchcards use std::collections::HashSet; use itertools::Itertools; pub const DAY: u8 = 4; +pub const TITLE: &str = "Scratchcards"; fn parse(line: &str) -> (HashSet, HashSet) { let (_, tmp) = line.split_once(": ").unwrap(); diff --git a/src/day5.rs b/src/day5.rs index 0f08fc0..d23f8e0 100644 --- a/src/day5.rs +++ b/src/day5.rs @@ -6,6 +6,7 @@ use itertools::Itertools; use rangemap::{RangeMap, RangeSet}; pub const DAY: u8 = 5; +pub const TITLE: &str = "If You Give A Seed A Fertilizer"; #[derive(Debug)] struct Parsed { diff --git a/src/day6.rs b/src/day6.rs index 0724000..e0825dd 100644 --- a/src/day6.rs +++ b/src/day6.rs @@ -1,10 +1,11 @@ -//! Day 6 +//! Day 6: Wait For It use std::ops::RangeInclusive; use itertools::Itertools; pub const DAY: u8 = 6; +pub const TITLE: &str = "Wait For It"; #[derive(Debug)] struct Race { diff --git a/src/day7.rs b/src/day7.rs index 3a7c7b1..e46b3a4 100644 --- a/src/day7.rs +++ b/src/day7.rs @@ -1,4 +1,4 @@ -//! Day 7 +//! Day 7: Camel Cards use std::{ cmp::Ordering, @@ -8,6 +8,7 @@ use std::{ use itertools::Itertools; pub const DAY: u8 = 7; +pub const TITLE: &str = "Camel Cards"; #[derive(Debug, PartialEq, Eq)] struct Hand(String); diff --git a/src/day8.rs b/src/day8.rs index 80aa4e4..b4d8950 100644 --- a/src/day8.rs +++ b/src/day8.rs @@ -1,10 +1,11 @@ -//! Day 8 +//! Day 8: Haunted Wasteland use std::collections::{HashMap, HashSet}; use itertools::Itertools; pub const DAY: u8 = 8; +pub const TITLE: &str = "Haunted Wasteland"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Direction { diff --git a/src/main.rs b/src/main.rs index 1c2cc10..20870da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,7 @@ type SolveFn = Box) -> String>; struct Day { n: u8, + title: &'static str, p1: SolveFn, p2: SolveFn, } @@ -61,6 +62,7 @@ macro_rules! days { vec![ $(Day { n: $module::DAY, + title: $module::TITLE, p1: Box::new($module::part1), p2: Box::new($module::part2), },)* @@ -70,7 +72,6 @@ macro_rules! days { } days! { - day0, day1, day2, day3, @@ -98,13 +99,15 @@ fn main() { println!("✨The result for day {}/{:?} is✨", day, part); println!("{}", res); } else { - println!("AoC 23 | Part one | Part two"); - println!("=======|======================|======================"); + println!("Advent of Code 2023 | Part one | Part two"); + println!("=============================|======================|======================"); for day in days { + let title = &day.title[..day.title.len().min(20)]; + let t = Instant::now(); let res_a = get_result(&day, 1); let res_a = format!("{res_a} ({}ms)", t.elapsed().as_millis()); - print!("Day {:2} | {res_a:20}", day.n); + print!("Day {:2}: {title:20} | {res_a:20}", day.n); let t = Instant::now(); let res_b = get_result(&day, 2);