Compare commits

...

4 commits

Author SHA1 Message Date
acd627a008
feat: add titles for days 2023-12-09 01:53:48 +01:00
0b2475d2bf
improve day 5 2023-12-09 01:43:07 +01:00
fd056ae79e
update days macro 2023-12-08 22:07:58 +01:00
452b15fd03
add table output format 2023-12-08 18:09:18 +01:00
10 changed files with 172 additions and 73 deletions

View file

@ -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>) -> String { pub fn part1(input: Vec<String>) -> String {
input[0].to_owned() input[0].to_owned()
@ -12,8 +15,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 0;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "123"); assert_eq!(part1(crate::read_example(DAY, 1)), "123");

View file

@ -1,7 +1,10 @@
//! Day 1 //! Day 1: Trebuchet?!
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
pub const DAY: u8 = 1;
pub const TITLE: &str = "Trebuchet?!";
pub fn part1(input: Vec<String>) -> String { pub fn part1(input: Vec<String>) -> String {
input input
.iter() .iter()
@ -47,8 +50,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 1;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "142"); assert_eq!(part1(crate::read_example(DAY, 1)), "142");

View file

@ -1,8 +1,11 @@
//! Day 2 //! Day 2: Cube Conundrum
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
pub const DAY: u8 = 2;
pub const TITLE: &str = "Cube Conundrum";
#[derive(Debug)] #[derive(Debug)]
struct Game { struct Game {
id: u32, id: u32,
@ -94,8 +97,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 2;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "8"); assert_eq!(part1(crate::read_example(DAY, 1)), "8");

View file

@ -1,4 +1,4 @@
//! Day 3 //! Day 3: Gear Ratios
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -6,6 +6,9 @@ use itertools::Itertools;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
pub const DAY: u8 = 3;
pub const TITLE: &str = "Gear Ratios";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Pos { struct Pos {
x: i64, x: i64,
@ -181,8 +184,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 3;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "4361"); assert_eq!(part1(crate::read_example(DAY, 1)), "4361");

View file

@ -1,9 +1,12 @@
//! Day 4 //! Day 4: Scratchcards
use std::collections::HashSet; use std::collections::HashSet;
use itertools::Itertools; use itertools::Itertools;
pub const DAY: u8 = 4;
pub const TITLE: &str = "Scratchcards";
fn parse(line: &str) -> (HashSet<u32>, HashSet<u32>) { fn parse(line: &str) -> (HashSet<u32>, HashSet<u32>) {
let (_, tmp) = line.split_once(": ").unwrap(); let (_, tmp) = line.split_once(": ").unwrap();
let (win, got) = tmp.split_once(" | ").unwrap(); let (win, got) = tmp.split_once(" | ").unwrap();
@ -62,8 +65,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 4;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "13"); assert_eq!(part1(crate::read_example(DAY, 1)), "13");

View file

@ -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 itertools::Itertools;
use rangemap::{RangeMap, RangeSet}; use rangemap::{RangeMap, RangeSet};
pub const DAY: u8 = 5;
pub const TITLE: &str = "If You Give A Seed A Fertilizer";
#[derive(Debug)] #[derive(Debug)]
struct Parsed { struct Parsed {
seeds: Vec<u64>, seeds: Vec<u64>,
maps: Vec<RangeMap<u64, u64>>, maps: Vec<RangeMap<u64, Range<u64>>>,
} }
impl Parsed { impl Parsed {
@ -16,11 +19,45 @@ impl Parsed {
let mut s = seed; let mut s = seed;
for map in &self.maps { for map in &self.maps {
if let Some((rng, dst)) = map.get_key_value(&s) { if let Some((rng, dst)) = map.get_key_value(&s) {
s = dst + (s - rng.start); s = dst.start + (s - rng.start);
} }
} }
s s
} }
/// Transform the list of seed maps into a single map
fn transform(&self) -> RangeMap<u64, Range<u64>> {
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<String>) -> Parsed { fn parse(input: Vec<String>) -> Parsed {
@ -56,7 +93,10 @@ fn parse(input: Vec<String>) -> Parsed {
.collect_tuple() .collect_tuple()
.unwrap(); .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() { if !map.is_empty() {
maps.push(map); maps.push(map);
@ -67,11 +107,15 @@ fn parse(input: Vec<String>) -> Parsed {
pub fn part1(input: Vec<String>) -> String { pub fn part1(input: Vec<String>) -> String {
let parsed = parse(input); let parsed = parse(input);
let tf = parsed.transform();
parsed parsed
.seeds .seeds
.iter() .iter()
.map(|s| parsed.lookup(*s)) .map(|s| {
let (rng, dst) = tf.get_key_value(s).unwrap();
dst.start + (s - rng.start)
})
.min() .min()
.unwrap() .unwrap()
.to_string() .to_string()
@ -79,6 +123,7 @@ pub fn part1(input: Vec<String>) -> String {
pub fn part2(input: Vec<String>) -> String { pub fn part2(input: Vec<String>) -> String {
let parsed = parse(input); let parsed = parse(input);
let tf = parsed.transform();
let ranges = parsed let ranges = parsed
.seeds .seeds
@ -87,24 +132,40 @@ pub fn part2(input: Vec<String>) -> String {
.map(|(a, b)| (*a)..(*a + *b)) .map(|(a, b)| (*a)..(*a + *b))
.collect::<RangeSet<_>>(); .collect::<RangeSet<_>>();
tf.into_iter()
.sorted_by_key(|(_, soils)| soils.start)
.find_map(|(seeds, soils)| {
ranges ranges
.into_iter() .iter()
.flat_map(|r| { .find_map(|r| lowest_intersection(r, &seeds))
dbg!(&r); .map(|seed| soils.start + (seed - seeds.start))
r.into_iter()
}) })
.map(|s| parsed.lookup(s))
.min()
.unwrap() .unwrap()
.to_string() .to_string()
} }
fn rlen(range: &Range<u64>) -> u64 {
range.end - range.start
}
fn lowest_intersection(r1: &Range<u64>, r2: &Range<u64>) -> Option<u64> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 5;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "35"); assert_eq!(part1(crate::read_example(DAY, 1)), "35");

View file

@ -1,9 +1,12 @@
//! Day 6 //! Day 6: Wait For It
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use itertools::Itertools; use itertools::Itertools;
pub const DAY: u8 = 6;
pub const TITLE: &str = "Wait For It";
#[derive(Debug)] #[derive(Debug)]
struct Race { struct Race {
t: u64, t: u64,
@ -67,8 +70,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 6;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "288"); assert_eq!(part1(crate::read_example(DAY, 1)), "288");

View file

@ -1,4 +1,4 @@
//! Day 7 //! Day 7: Camel Cards
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
@ -7,6 +7,9 @@ use std::{
use itertools::Itertools; use itertools::Itertools;
pub const DAY: u8 = 7;
pub const TITLE: &str = "Camel Cards";
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct Hand(String); struct Hand(String);
@ -133,8 +136,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 7;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "6440"); assert_eq!(part1(crate::read_example(DAY, 1)), "6440");

View file

@ -1,9 +1,12 @@
//! Day 8 //! Day 8: Haunted Wasteland
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use itertools::Itertools; use itertools::Itertools;
pub const DAY: u8 = 8;
pub const TITLE: &str = "Haunted Wasteland";
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction { enum Direction {
Right, Right,
@ -90,8 +93,6 @@ pub fn part2(input: Vec<String>) -> String {
mod tests { mod tests {
use super::*; use super::*;
const DAY: u8 = 8;
#[test] #[test]
fn t_example1() { fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "2"); assert_eq!(part1(crate::read_example(DAY, 1)), "2");

View file

@ -4,8 +4,10 @@ use std::{
fs::File, fs::File,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
path::Path, path::Path,
time::Instant,
}; };
use once_cell::sync::Lazy;
use path_macro::path; use path_macro::path;
pub(crate) fn read_input(day: u8) -> Vec<String> { pub(crate) fn read_input(day: u8) -> Vec<String> {
@ -34,55 +36,83 @@ fn read_input_file<P: AsRef<Path>>(path: P) -> Vec<String> {
input 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<dyn Fn(Vec<String>) -> String>;
struct Day {
n: u8,
title: &'static str,
p1: SolveFn,
p2: SolveFn,
}
macro_rules! days { macro_rules! days {
( $($n:expr, $module:ident,)* ) => { ( $($module:ident,)* ) => {
$(mod $module;)* $(mod $module;)*
fn get_result(day: u8, part: u8) -> String { fn days() -> Vec<Day> {
let input = read_input(day); vec![
$(Day {
match day { n: $module::DAY,
$( title: $module::TITLE,
$n => match part { p1: Box::new($module::part1),
2 => $module::part2(input), p2: Box::new($module::part2),
_ => $module::part1(input), },)*
} ]
)*
_ => panic!("day {} missing", day),
}
} }
}; };
} }
days! { days! {
0, day0, day1,
1, day1, day2,
2, day2, day3,
3, day3, day4,
4, day4, day5,
5, day5, day6,
6, day6, day7,
7, day7, day8,
8, day8,
} }
fn main() { fn main() {
let mut args = std::env::args(); let mut args = std::env::args();
args.next(); args.next();
let days = days();
let day = args if let Some(day) = args.next().and_then(|a| a.parse::<u8>().ok()) {
.next()
.expect("no day")
.parse::<u8>()
.expect("invalid day");
let part = args let part = args
.next() .next()
.and_then(|arg| arg.parse::<u8>().ok().map(|p| p.clamp(1, 2))) .and_then(|arg| arg.parse::<u8>().ok().map(|p| p.clamp(1, 2)))
.unwrap_or(1); .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!("✨The result for day {}/{:?} is✨", day, part);
println!("{}", res); 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)];
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}")
}
}
} }