aoc.2022/src/main.rs

458 lines
12 KiB
Rust

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<u64> for Output {
fn from(n: u64) -> Self {
Self::Num(n)
}
}
impl From<String> 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::<u64>().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::<u64>().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::<u64>().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::<u64>().into()
},
],
[
// day 3 part 1
|input| {
input
.lines()
.map(|line| {
let line = String::from(line);
let rs = Rucksack::new(line);
rs.score()
})
.sum::<u64>().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::<u64>().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()
},
],
[
// day 6 part 1
|input| {
find_start_marker(input, 4).into()
},
// day 6 part 2
|input| {
find_start_marker(input, 14).into()
}
]
}
fn find_start_marker(input: String, window_size: usize) -> u64 {
for i in 0..input.len() - window_size {
let window = &input[i..i + window_size];
let set: HashSet<char> = HashSet::from_iter(window.chars());
if set.len() == window_size {
return (i + window_size) as u64;
}
}
unreachable!()
}
fn run_rules_9001(mut stacks: Vec<Vec<char>>, rules: Vec<Rule>) -> 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<Vec<char>>, rules: Vec<Rule>) -> 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<Vec<char>> {
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<usize>) -> Self {
Self {
n: raw[0],
from: raw[1],
to: raw[2],
}
}
}
fn make_rules(input: &str) -> Vec<Rule> {
input
.lines()
.map(|line| {
line.split_whitespace()
.skip(1)
.step_by(2)
.map(|num| num.parse::<usize>().unwrap())
.collect()
})
.map(Rule::new)
.collect()
}
fn make_ranges(input: String) -> Vec<(Range<u64>, Range<u64>)> {
input
.lines()
.map(|line| {
line.split(',')
.map(|range| {
range
.split('-')
.map(|num| num.parse::<u64>().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<u64>, r2: &Range<u64>) -> bool {
r1.start() <= r2.start() && r1.end() >= r2.end()
}
fn range_overlaps(r1: &Range<u64>, r2: &Range<u64>) -> 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<char>,
second: HashSet<char>,
}
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<char>,
second: HashSet<char>,
third: HashSet<char>,
}
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<char> {
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
}
}