aoc/2023/src/bin/day15.rs
2023-12-15 11:34:00 +01:00

160 lines
4.1 KiB
Rust

#![feature(test)]
use std::cmp::Ordering;
use anyhow::Result;
use aoc::Solver;
// -- Runners --
fn main() -> Result<()> {
Day::solve()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_test1() -> Result<()> {
Day::test(Day::part1, "test-1", 1320)
}
#[test]
fn part1_solution() -> Result<()> {
Day::test(Day::part1, "input", 511416)
}
#[test]
fn part2_test1() -> Result<()> {
Day::test(Day::part2, "test-1", 145)
}
#[test]
fn part2_solution() -> Result<()> {
Day::test(Day::part2, "input", 290779)
}
// Benchmarks
extern crate test;
#[bench]
#[ignore]
fn part1_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part1, b)
}
#[bench]
#[ignore]
fn part2_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part2, b)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Action<'a> {
Add { label: &'a str, focal_length: usize },
Remove { label: &'a str },
}
impl<'a> Action<'a> {
fn index(&self) -> usize {
match self {
Action::Add { label, .. } | Action::Remove { label } => label
.chars()
.map(|c| c as usize)
.fold(0, |acc, num| ((acc + num) * 17) % 256),
}
}
}
// -- Solution --
pub struct Day;
impl aoc::Solver for Day {
type Output1 = usize;
type Output2 = usize;
fn day() -> u8 {
15
}
fn part1(input: &str) -> Self::Output1 {
input
.trim()
.split(',')
.map(|sequence| {
sequence
.chars()
.map(|c| c as usize)
.fold(0, |acc, num| ((acc + num) * 17) % 256)
})
.sum()
}
fn part2(input: &str) -> Self::Output2 {
let actions: Vec<_> = input
.trim()
.split(',')
.map(|sequence| {
if sequence.ends_with('-') {
let label = sequence.split_once('-').unwrap().0;
Action::Remove { label }
} else {
let (label, focal_length) = sequence.split_once('=').unwrap();
let focal_length = focal_length.parse().unwrap();
Action::Add {
label,
focal_length,
}
}
})
.collect();
let mut boxes: [Vec<(&str, usize)>; 256] = std::array::from_fn(|_| Vec::new());
for action in &actions {
let index = action.index();
match action {
Action::Add {
label,
focal_length,
} => {
// NOTE: Using a library like OrderedMap would make this trivially easy
if let Some(position) = boxes[index]
.iter()
.position(|lens| lens.0.cmp(label) == Ordering::Equal)
{
// If a lens with this label is already in the box, replace it
boxes[index][position].1 = *focal_length;
} else {
// Otherwise add it to the end of the box
boxes[index].push((label, *focal_length));
}
}
Action::Remove { label } => {
if let Some(position) = boxes[index]
.iter()
.position(|lens| lens.0.cmp(label) == Ordering::Equal)
{
// A lens with the label exists, remove it
boxes[index].remove(position);
}
}
}
}
boxes
.iter()
.enumerate()
.map(|(index, b)| {
b.iter()
.enumerate()
.map(|(position, (_, focal_length))| {
(1 + index) * (1 + position) * focal_length
})
.sum::<usize>()
})
.sum()
}
}