diff --git a/src/day0.rs b/src/day0.rs index c1b81ce..77be5c1 100644 --- a/src/day0.rs +++ b/src/day0.rs @@ -1,4 +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() @@ -12,8 +15,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..053ea65 100644 --- a/src/day1.rs +++ b/src/day1.rs @@ -1,7 +1,10 @@ -//! 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 .iter() @@ -47,8 +50,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..a08eb7c 100644 --- a/src/day2.rs +++ b/src/day2.rs @@ -1,8 +1,11 @@ -//! 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 { id: u32, @@ -94,8 +97,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..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}; @@ -6,6 +6,9 @@ use itertools::Itertools; 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 { x: i64, @@ -181,8 +184,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..d66c436 100644 --- a/src/day4.rs +++ b/src/day4.rs @@ -1,9 +1,12 @@ -//! 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(); let (win, got) = tmp.split_once(" | ").unwrap(); @@ -62,8 +65,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..d23f8e0 100644 --- a/src/day5.rs +++ b/src/day5.rs @@ -1,14 +1,17 @@ -//! 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}; +pub const DAY: u8 = 5; +pub const TITLE: &str = "If You Give A Seed A Fertilizer"; + #[derive(Debug)] struct Parsed { seeds: Vec, - maps: Vec>, + maps: Vec>>, } impl Parsed { @@ -16,11 +19,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 { @@ -56,7 +93,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); @@ -67,11 +107,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() @@ -79,6 +123,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 @@ -87,24 +132,40 @@ 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::*; - 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..e0825dd 100644 --- a/src/day6.rs +++ b/src/day6.rs @@ -1,9 +1,12 @@ -//! 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 { t: u64, @@ -67,8 +70,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..e46b3a4 100644 --- a/src/day7.rs +++ b/src/day7.rs @@ -1,4 +1,4 @@ -//! Day 7 +//! Day 7: Camel Cards use std::{ cmp::Ordering, @@ -7,6 +7,9 @@ use std::{ use itertools::Itertools; +pub const DAY: u8 = 7; +pub const TITLE: &str = "Camel Cards"; + #[derive(Debug, PartialEq, Eq)] struct Hand(String); @@ -133,8 +136,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..b4d8950 100644 --- a/src/day8.rs +++ b/src/day8.rs @@ -1,9 +1,12 @@ -//! 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 { Right, @@ -90,8 +93,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 9e52d02..20870da 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,55 +36,83 @@ fn read_input_file>(path: P) -> Vec { input } +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, + title: &'static str, + p1: SolveFn, + p2: SolveFn, +} + macro_rules! days { - ( $($n:expr, $module:ident,)* ) => { + ( $($module:ident,)* ) => { $(mod $module;)* - 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, + title: $module::TITLE, + 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, + day1, + day2, + day3, + day4, + day5, + day6, + day7, + day8, } fn main() { let mut args = std::env::args(); args.next(); + let days = days(); - 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 dobj = days.iter().find(|d| d.n == day).expect("Day not found"); + let res = get_result(dobj, part); - let res = get_result(day, part); + println!("✨The result for day {}/{:?} is✨", day, part); + println!("{}", res); + } else { + println!("Advent of Code 2023 | Part one | Part two"); + println!("=============================|======================|======================"); + for day in days { + let title = &day.title[..day.title.len().min(20)]; - println!("✨The result for day {}/{:?} is✨", day, part); - println!("{}", res); + 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}: {title:20} | {res_a:20}", day.n); + + 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}") + } + } }