advent_of_code/2024/day09_disk-fragmenter/src/lib.rs
2024-12-09 16:16:56 +01:00

122 lines
3.6 KiB
Rust

use core::fmt::Display;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
ParseCharError(char),
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseCharError(e) => write!(f, "Unable to parse {e} into a digit."),
}
}
}
type Id=usize;
type Size=u8;
#[derive(Clone, Debug)]
struct Filesystem {
// All the FS blocks. `None` for empty blocks, `Some(file_id, file_size)` for files.
blocks: Vec<Option<(Id, Size)>>,
// List of the empty blocks in order. Format: (first_block_idx, size)
free: Vec<(usize, Size)>,
}
impl TryFrom<&str> for Filesystem {
type Error=ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut file = true;
let mut id = 0;
let mut blocks = Vec::new();
let mut free = Vec::new();
for c in value.chars() {
if !c.is_ascii_digit() {
return Err(Self::Error::ParseCharError(c));
}
let size = c as Size - b'0';
let entry = if file { Some((id, size)) } else { None };
file = if file {
id += 1;
false
} else {
if size != 0 {
free.push((blocks.len(), size));
}
true
};
(0..size).for_each(|_| blocks.push(entry));
}
Ok(Self { blocks, free, })
}
}
impl Filesystem {
fn compress(&mut self) {
while let Some(empty) = self.blocks.iter().position(|b| b.is_none()) {
let last = self.blocks.pop().unwrap();
if last.is_some() {
self.blocks[empty] = last;
}
self.free = Vec::new();
}
}
fn compress_unfragmented(&mut self) {
for id in (0..=self.blocks.iter().rfind(|file| file.is_some()).unwrap_or(&Some((0, 0))).unwrap().0).rev() {
let old_idx = self.blocks.iter().position(|file| file.is_some() && file.unwrap().0 == id).unwrap();
let file_size = self.blocks[old_idx].unwrap().1;
if let Some(free_entry) = self.free.iter_mut().find(|(_block_id, free_size)| *free_size >= file_size) {
let new_idx = free_entry.0;
if new_idx < old_idx {
free_entry.0 += file_size as usize;
free_entry.1 -= file_size;
(0..file_size as usize).for_each(|offset| {
self.blocks[old_idx + offset] = None;
self.blocks[new_idx + offset] = Some((id, file_size));
});
}
}
}
}
fn checksum(&self) -> usize {
self.blocks.iter().enumerate().map(|(idx, file)| idx * file.unwrap_or((0, 0)).0).sum()
}
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let mut fs1 = Filesystem::try_from(input)?;
let mut fs2 = fs1.clone();
fs1.compress();
fs2.compress_unfragmented();
let first = fs1.checksum();
let second = fs2.checksum();
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((1928, 2858)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((6607511583593, 6636608781232)));
}
}