Day 5
This commit is contained in:
parent
05994ef2be
commit
fe8a63cd27
1 changed files with 202 additions and 0 deletions
202
src/bin/day5.rs
Normal file
202
src/bin/day5.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use aoc_2025::{load, print_res};
|
||||
use bstr::BString;
|
||||
|
||||
type Parsed = InventoryState;
|
||||
|
||||
struct IdRange {
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for IdRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}..={}", self.start, self.end)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InventoryState {
|
||||
fresh: Vec<IdRange>,
|
||||
avail: Vec<u64>,
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
|
||||
let Some((fresh, avail)) = str::from_utf8(input)?.split_once("\n\n") else {
|
||||
color_eyre::eyre::bail!("No blank line found");
|
||||
};
|
||||
|
||||
Ok(InventoryState {
|
||||
fresh: fresh
|
||||
.lines()
|
||||
.map(|l| {
|
||||
let Some((start, end)) = l.split_once('-') else {
|
||||
color_eyre::eyre::bail!("Invalid ID range: {l}");
|
||||
};
|
||||
Ok(IdRange {
|
||||
start: start.parse()?,
|
||||
end: end.parse()?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
avail: avail
|
||||
.lines()
|
||||
.map(|id| id.parse())
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct IntervalSet(Vec<IdRange>);
|
||||
|
||||
impl IntervalSet {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
fn insert_at(&mut self, i: usize, range: IdRange) {
|
||||
if i == 0 {
|
||||
if self.0[0].start <= range.end {
|
||||
todo!()
|
||||
} else {
|
||||
self.0.insert(0, range);
|
||||
}
|
||||
} else if i == self.0.len() {
|
||||
if self.0[i - 1].end >= range.start {
|
||||
todo!()
|
||||
} else {
|
||||
self.0.push(range);
|
||||
}
|
||||
} else {
|
||||
// We are in the middle of the array
|
||||
if self.0[i - 1].end >= range.start {
|
||||
// The range overlaps with the previous one
|
||||
|
||||
if range.end <= self.0[i - 1].end {
|
||||
// The new range is completly contained in the previous one
|
||||
} else {
|
||||
// The range overlaps with at least the previous one
|
||||
if range.end >= self.0[i].start {
|
||||
// Part of the range overlaps the next range
|
||||
let next = self.0.remove(i);
|
||||
|
||||
if next.end < range.end {
|
||||
// The range might overlap with two ranges
|
||||
todo!()
|
||||
} else {
|
||||
// Merge the three ranges together
|
||||
self.0[i - 1].end = next.end;
|
||||
}
|
||||
} else {
|
||||
// Concatenate this range with the next one
|
||||
self.0[i - 1].end = range.end;
|
||||
}
|
||||
}
|
||||
} else if self.0[i].start <= range.end {
|
||||
// The range overlaps with the next one
|
||||
|
||||
self.0[i].start = self.0[i].start.min(range.start);
|
||||
|
||||
if range.end <= self.0[i].end {
|
||||
// The range does not go further than the end of the next
|
||||
} else {
|
||||
// We have only inserted up to the end of the next range, insert the
|
||||
// rest
|
||||
self.insert(IdRange {
|
||||
start: self.0[i].end + 1,
|
||||
end: range.end,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// We overlap with neither ranges
|
||||
self.0.insert(i, range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, range: IdRange) {
|
||||
if self.0.is_empty() {
|
||||
self.0.push(range);
|
||||
return;
|
||||
}
|
||||
|
||||
match self.0.binary_search_by_key(&range.start, |r| r.start) {
|
||||
// Two starts overlap
|
||||
Ok(i) => {
|
||||
if range.end <= self.0[i].end {
|
||||
// Nothing to do, the range is contained inside the existing one
|
||||
} else {
|
||||
// Replace the existing range with this new superset
|
||||
self.0.remove(i);
|
||||
self.insert(range);
|
||||
}
|
||||
}
|
||||
Err(i) => self.insert_at(i, range),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&mut self, value: u64) -> bool {
|
||||
self.0
|
||||
.binary_search_by(|r| {
|
||||
if value < r.start {
|
||||
std::cmp::Ordering::Greater
|
||||
} else if value > r.end {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn length(&self) -> u64 {
|
||||
self.0.iter().map(|r| r.end - r.start + 1).sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn part1(input: Parsed) {
|
||||
let mut ranges = IntervalSet::new();
|
||||
|
||||
for fresh in input.fresh.into_iter() {
|
||||
ranges.insert(fresh);
|
||||
}
|
||||
|
||||
let fresh = input.avail.iter().filter(|&&v| ranges.contains(v)).count();
|
||||
|
||||
print_res!("Fresh ingredient count: {fresh}");
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn part2(input: Parsed) {
|
||||
let mut ranges = IntervalSet::new();
|
||||
|
||||
for fresh in input.fresh.into_iter() {
|
||||
ranges.insert(fresh);
|
||||
}
|
||||
|
||||
print_res!("Total fresh ingredient count: {}", ranges.length());
|
||||
}
|
||||
|
||||
pub fn main() -> color_eyre::Result<()> {
|
||||
let context = load()?;
|
||||
|
||||
let start = Instant::now();
|
||||
let parsed = parsing(&context.input)?;
|
||||
let elapsed = humantime::format_duration(start.elapsed());
|
||||
|
||||
let start = Instant::now();
|
||||
if context.part == 1 {
|
||||
part1(parsed);
|
||||
} else {
|
||||
part2(parsed);
|
||||
}
|
||||
let elapsed_part = humantime::format_duration(start.elapsed());
|
||||
|
||||
println!(" Parsing: {elapsed}");
|
||||
println!(" Solving: {elapsed_part}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue