Files
aoc2025/src/day5.rs
2025-12-05 00:26:43 -08:00

172 lines
4.1 KiB
Rust

use std::cmp::{max, min};
use std::fmt::Debug;
use std::ops::RangeInclusive;
use aoc_runner_derive::{aoc, aoc_generator};
struct Database {
fresh_ingredients: Vec<RangeInclusive<u64>>,
available_ingredients: Vec<u64>,
}
struct Database2 {
fresh_ingredients: Vec<Span>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Span {
start: u64,
end: u64,
}
impl From<(u64, u64)> for Span {
fn from(value: (u64, u64)) -> Self {
Self {
start: value.0,
end: value.1,
}
}
}
impl Span {
fn len(&self) -> u64 {
self.end - self.start + 1
}
}
struct RangeSet {
ranges: Vec<Span>,
}
impl RangeSet {
fn new() -> Self {
Self { ranges: Vec::new() }
}
fn simplify(&mut self) {
// sort the ranges by start
if self.ranges.len() < 2 {
return;
}
let mut modified = true;
while modified {
let mut new_ranges = Vec::new();
modified = false;
self.ranges.sort_by_key(|s| s.start);
for chunk in self.ranges.chunks(2) {
if chunk.len() == 1 {
new_ranges.push(chunk[0].clone());
continue;
}
let (l, r) = (&chunk[0], &chunk[1]);
if r.start <= l.end + 1 {
modified = true;
new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into())
} else {
new_ranges.push(l.clone());
new_ranges.push(r.clone());
}
}
if !self.ranges.len().is_multiple_of(2) && new_ranges.len() >= 2 {
let (l, r) = (new_ranges.pop().unwrap(), new_ranges.pop().unwrap());
if r.start <= l.end + 1 && r.end >= l.start - 1 {
modified = true;
new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into())
} else {
new_ranges.push(l.clone());
new_ranges.push(r.clone());
}
}
self.ranges = new_ranges;
}
}
fn add(&mut self, s: Span) {
self.ranges.push(s);
self.simplify()
}
}
#[aoc_generator(day5, part1)]
fn parse(input: &str) -> Database {
let mut fresh_ingredients = Vec::new();
let mut available_ingredients = Vec::new();
let mut parsing_ranges = true;
for line in input.lines() {
if line.is_empty() {
parsing_ranges = false;
continue;
}
if parsing_ranges {
let (start, end) = line.split_once('-').unwrap();
fresh_ingredients.push(RangeInclusive::new(
start.parse().unwrap(),
end.parse().unwrap(),
));
} else {
available_ingredients.push(line.parse().unwrap())
}
}
Database {
fresh_ingredients,
available_ingredients,
}
}
#[aoc_generator(day5, part2)]
fn parse2(input: &str) -> Database2 {
let mut fresh_ingredients = Vec::new();
for line in input.lines() {
if line.is_empty() {
return Database2 { fresh_ingredients };
}
let (start, end) = line.split_once('-').unwrap();
fresh_ingredients.push((start.parse().unwrap(), end.parse().unwrap()).into());
}
Database2 { fresh_ingredients }
}
#[aoc(day5, part1)]
fn part1(input: &Database) -> u64 {
input
.available_ingredients
.iter()
.filter(|i| input.fresh_ingredients.iter().any(|r| r.contains(i)))
.count() as u64
}
#[aoc(day5, part2)]
fn part2(input: &Database2) -> u64 {
let mut all_ingredients = RangeSet::new();
for r in &input.fresh_ingredients {
all_ingredients.add(r.clone())
}
all_ingredients.ranges.iter().map(|r| r.len()).sum::<u64>()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "3-5
10-14
16-20
12-18
1
5
8
11
17
32";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EXAMPLE)), 3);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse2(EXAMPLE)), 14);
}
}