use std::collections::HashSet; use std::fs; use std::ops::RangeInclusive as Range; use std::{cmp::Ordering, fmt::Display}; use anyhow::Error; use chrono::{Datelike, Local}; use pico_args::Arguments; fn main() -> Result<(), Error> { let mut args = Arguments::from_env(); let small = args.contains("--small"); let part = args.opt_free_from_str()?.unwrap_or(1); let day = args .opt_free_from_str()? .unwrap_or_else(|| Local::now().day()); let small = if small { ".small" } else { "" }; let path = format!("inputs/{day}{small}.txt"); let input = fs::read_to_string(path)?; println!("running day {day} part {part}"); let result = SOLUTIONS[day as usize - 1][part as usize - 1](input); println!("{result}"); Ok(()) } macro_rules! solutions { ($($all:expr),*) => { const DAYS: usize = 0 $( + solutions!(@expand $all 1) )*; const SOLUTIONS: [Day; DAYS] = [ $($all),* ]; }; (@expand $ignore:tt $e:expr) => {$e}; } enum Output { Num(u64), Str(String), } impl Display for Output { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Output::Num(n) => write!(f, "{n}"), Output::Str(s) => write!(f, "{s}"), } } } impl From for Output { fn from(n: u64) -> Self { Self::Num(n) } } impl From for Output { fn from(s: String) -> Self { Self::Str(s) } } type Part = fn(String) -> Output; type Day = [Part; 2]; solutions! { [ // day 1 part 1 |input| { let lines = input.lines(); let (max, _) = lines.fold((0, 0), |(max, sum), line| { if line.is_empty() { (max.max(sum), 0) } else { let cal = line.parse::().expect("Got bad line"); (max, sum + cal) } }); max.into() }, // day 1 part 2 |input| { let lines = input.lines(); let (a, b, c, _) = lines.fold((0, 0, 0, 0), |(a, b, c, sum), line| { if line.trim().is_empty() { if sum > a { (sum, a, b, 0) } else if sum > b { (a, sum, b, 0) } else if sum > c { (a, b, sum, 0) } else { (a, b, c, 0) } } else { let cal = line.trim().parse::().expect("Got bad line"); (a, b, c, sum + cal) } }); (a + b + c).into() }, ], [ // day 2 part 1 |input| { input .lines() .map(|line| { let (you, me) = line.split_once(' ').expect("oof"); let (you, me) = (RPS::from_str(you), RPS::from_str(me)); RPS::score(you, me) }) .sum::().into() }, // day 2 part 2 |input| { input .lines() .map(|line| { let (you, out) = line.split_once(' ').expect("oof"); let (you, out) = (RPS::from_str(you), Outcome::from_str(out)); let me = out.requires(you); RPS::score(you, me) }) .sum::().into() }, ], [ // day 3 part 1 |input| { input .lines() .map(|line| { let line = String::from(line); let rs = Rucksack::new(line); rs.score() }) .sum::().into() }, // day 3 part 2 |input| { let lines: Vec<_> = input.lines().collect(); lines .chunks(3) .map(|chunks| match chunks { [f, s, t] => { let rs = Rucksacks::new(String::from(*f), String::from(*s), String::from(*t)); rs.score() } _ => unreachable!("fooey"), }) .sum::().into() }, ], [ // day 4 part 1 |input| { (make_ranges(input) .iter() .filter(|(first, second)| { range_contains(first, second) || range_contains(second, first) }) .count() as u64).into() }, // day 4 part 2 |input| { (make_ranges(input) .iter() .filter(|(first, second)| range_overlaps(first, second) || range_overlaps(second, first)) .count() as u64).into() }, ], [ // day 5 part 1 |input| { let midpt = input.find("\n\n").unwrap(); let stacks = make_stacks(&input[..=midpt]); let rules = make_rules(&input[midpt + 2..]); run_rules(stacks, rules).into() }, // day 5 part 2 |input| { let midpt = input.find("\n\n").unwrap(); let stacks = make_stacks(&input[..=midpt]); let rules = make_rules(&input[midpt + 2..]); run_rules_9001(stacks, rules).into() }, ] } fn run_rules_9001(mut stacks: Vec>, rules: Vec) -> String { for rule in rules { let total = stacks[rule.from - 1].len(); let mut moved = stacks[rule.from - 1].drain((total - rule.n)..).collect(); stacks[rule.to - 1].append(&mut moved); } let mut s = String::new(); for stack in stacks { s.push(*stack.last().unwrap()) } s } fn run_rules(mut stacks: Vec>, rules: Vec) -> String { for rule in rules { for _ in 0..rule.n { let c = stacks[rule.from - 1].pop().unwrap(); stacks[rule.to - 1].push(c); } } let mut s = String::new(); for stack in stacks { s.push(*stack.last().unwrap()) } s } fn make_stacks(input: &str) -> Vec> { let n = (input.find('\n').unwrap()) / 4 + 1; let mut stacks = vec![vec![]; n]; input .lines() .rev() .filter(|line| line.trim().starts_with('[')) .for_each(|line| { for (idx, cr8) in line.chars().skip(1).step_by(4).enumerate() { if cr8 != ' ' { stacks[idx].push(cr8); } } }); stacks } #[derive(Debug)] struct Rule { n: usize, from: usize, to: usize, } impl Rule { fn new(raw: Vec) -> Self { Self { n: raw[0], from: raw[1], to: raw[2], } } } fn make_rules(input: &str) -> Vec { input .lines() .map(|line| { line.split_whitespace() .skip(1) .step_by(2) .map(|num| num.parse::().unwrap()) .collect() }) .map(Rule::new) .collect() } fn make_ranges(input: String) -> Vec<(Range, Range)> { input .lines() .map(|line| { line.split(',') .map(|range| { range .split('-') .map(|num| num.parse::().expect("bad num :/")) }) .map(|mut nums| { let lo = nums.next().expect("nolo :/"); let hi = nums.next().expect("nohi :/"); lo..=hi }) }) .map(|mut ranges| { let f = ranges.next().unwrap(); let s = ranges.next().unwrap(); (f, s) }) .collect() } fn range_contains(r1: &Range, r2: &Range) -> bool { r1.start() <= r2.start() && r1.end() >= r2.end() } fn range_overlaps(r1: &Range, r2: &Range) -> bool { r1.contains(r2.start()) } #[derive(Copy, Clone, PartialEq, Eq)] enum RPS { Rock, Paper, Scissors, } impl RPS { fn from_str(s: &str) -> Self { match s { "A" | "X" => RPS::Rock, "B" | "Y" => RPS::Paper, "C" | "Z" => RPS::Scissors, _ => unreachable!("you did a bad thing"), } } fn win(self) -> RPS { match self { RPS::Rock => RPS::Scissors, RPS::Paper => RPS::Rock, RPS::Scissors => RPS::Paper, } } fn lose(self) -> RPS { match self { RPS::Rock => RPS::Paper, RPS::Paper => RPS::Scissors, RPS::Scissors => RPS::Rock, } } fn cmp(self, other: Self) -> Ordering { if self == other { Ordering::Equal } else if self.win() == other { Ordering::Greater } else { Ordering::Less } } fn score(you: Self, me: Self) -> u64 { let base = match me { RPS::Rock => 1, RPS::Paper => 2, RPS::Scissors => 3, }; let win = match RPS::cmp(me, you) { Ordering::Less => 0, Ordering::Equal => 3, Ordering::Greater => 6, }; base + win } } #[derive(Copy, Clone)] enum Outcome { Win, Draw, Lose, } impl Outcome { fn from_str(s: &str) -> Self { match s { "X" => Outcome::Lose, "Y" => Outcome::Draw, "Z" => Outcome::Win, _ => unreachable!("yikes"), } } fn requires(self, you: RPS) -> RPS { match self { Outcome::Win => you.lose(), Outcome::Draw => you, Outcome::Lose => you.win(), } } } struct Rucksack { first: HashSet, second: HashSet, } impl Rucksack { fn new(mut input: String) -> Self { assert!(input.len() % 2 == 0); let second = input.split_off(input.len() / 2).chars().collect(); Self { first: input.chars().collect(), second, } } fn overlap(&self) -> HashSet<&char> { self.first.intersection(&self.second).collect() } fn score(&self) -> u64 { self.overlap().into_iter().copied().map(score_char).sum() } } struct Rucksacks { first: HashSet, second: HashSet, third: HashSet, } impl Rucksacks { fn new(f: String, s: String, t: String) -> Self { Rucksacks { first: f.chars().collect(), second: s.chars().collect(), third: t.chars().collect(), } } fn intersection(&self) -> HashSet { let start: HashSet<_> = self.first.intersection(&self.second).copied().collect(); start.intersection(&self.third).copied().collect() } fn score(&self) -> u64 { self.intersection().into_iter().map(score_char).sum() } } fn score_char(c: char) -> u64 { if c.is_lowercase() { c as u64 - 96 } else { c as u64 - 38 } }