2022-12-03 05:38:13 +00:00
|
|
|
use std::collections::HashSet;
|
2022-12-03 04:30:23 +00:00
|
|
|
use std::fs;
|
2022-12-04 05:32:14 +00:00
|
|
|
use std::{cmp::Ordering, ops::RangeInclusive as Range};
|
2022-12-03 04:30:23 +00:00
|
|
|
|
|
|
|
use anyhow::Error;
|
|
|
|
use chrono::{Datelike, Local};
|
|
|
|
use pico_args::Arguments;
|
|
|
|
|
2022-12-05 00:20:16 +00:00
|
|
|
macro_rules! solutions {
|
|
|
|
($($all:tt),*) => {
|
|
|
|
const DAYS: usize = 0 $( + solutions!(@expand $all 1) )*;
|
|
|
|
const SOLUTIONS: [Day; DAYS] = [
|
|
|
|
$($all),*
|
|
|
|
];
|
|
|
|
};
|
|
|
|
(@expand $ignore:tt $e:expr) => {$e};
|
2022-12-03 04:30:23 +00:00
|
|
|
}
|
|
|
|
|
2022-12-03 04:59:59 +00:00
|
|
|
type Part = fn(String) -> u64;
|
|
|
|
type Day = [Part; 2];
|
2022-12-05 00:20:16 +00:00
|
|
|
solutions! {
|
2022-12-03 04:59:59 +00:00
|
|
|
[
|
|
|
|
// 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
|
|
|
|
},
|
|
|
|
// 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
|
|
|
|
},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
// 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()
|
|
|
|
},
|
|
|
|
// 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()
|
|
|
|
},
|
|
|
|
],
|
2022-12-03 05:38:13 +00:00
|
|
|
[
|
|
|
|
// day 3 part 1
|
|
|
|
|input| {
|
|
|
|
input
|
|
|
|
.lines()
|
|
|
|
.map(|line| {
|
|
|
|
let line = String::from(line);
|
|
|
|
let rs = Rucksack::new(line);
|
|
|
|
rs.score()
|
|
|
|
})
|
|
|
|
.sum()
|
|
|
|
},
|
|
|
|
// 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()
|
|
|
|
},
|
|
|
|
],
|
2022-12-04 05:32:14 +00:00
|
|
|
[
|
|
|
|
// day 4 part 1
|
|
|
|
|input| {
|
|
|
|
make_ranges(input)
|
|
|
|
.iter()
|
|
|
|
.map(|(first, second)| {
|
|
|
|
range_contains(first, second) || range_contains(second, first)
|
|
|
|
})
|
|
|
|
.map(|b| if b { 1 } else { 0 })
|
|
|
|
.sum()
|
|
|
|
},
|
|
|
|
// day 4 part 2
|
|
|
|
|input| {
|
|
|
|
make_ranges(input)
|
|
|
|
.iter()
|
|
|
|
.map(|(first, second)| {
|
|
|
|
range_overlaps(first, second) || range_overlaps(second, first)
|
|
|
|
})
|
|
|
|
.map(|b| if b { 1 } else { 0 })
|
|
|
|
.sum()
|
|
|
|
},
|
2022-12-05 00:20:16 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
2022-12-03 04:30:23 +00:00
|
|
|
|
2022-12-04 05:32:14 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2022-12-03 04:30:23 +00:00
|
|
|
#[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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-03 05:38:13 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|