diff --git a/samples/day07.txt b/samples/day07.txt new file mode 100644 index 0000000..bcbb513 --- /dev/null +++ b/samples/day07.txt @@ -0,0 +1,23 @@ +$ cd / +$ ls +dir a +14848514 b.txt +8504156 c.dat +dir d +$ cd a +$ ls +dir e +29116 f +2557 g +62596 h.lst +$ cd e +$ ls +584 i +$ cd .. +$ cd .. +$ cd d +$ ls +4060174 j +8033020 d.log +5626152 d.ext +7214296 k \ No newline at end of file diff --git a/src/bin/day07.rs b/src/bin/day07.rs new file mode 100644 index 0000000..2fb7619 --- /dev/null +++ b/src/bin/day07.rs @@ -0,0 +1,242 @@ +use std::{cell::RefCell, cmp::min, collections::BTreeMap, rc::Rc, str::FromStr}; + +use aoc_2022::*; + +use color_eyre::{eyre::Result, Report}; +use itertools::Itertools; +use tracing::*; +use tracing_subscriber::EnvFilter; + +type FileHandle = Rc>; + +#[derive(Debug)] +enum AocFile { + File(usize), + Directory(BTreeMap), +} + +impl Default for AocFile { + fn default() -> Self { + AocFile::Directory(BTreeMap::new()) + } +} + +impl AocFile { + fn dir() -> FileHandle { + Rc::new(RefCell::new(AocFile::Directory(BTreeMap::new()))) + } + + fn file(size: usize) -> FileHandle { + Rc::new(RefCell::new(AocFile::File(size))) + } + + fn add_file(&mut self, name: &str, size: usize) { + if let AocFile::Directory(files) = self { + files.insert(name.to_string(), AocFile::file(size)); + return; + } + + panic!("cannot cd in file") + } + + fn cd(&mut self, dir: &str) -> FileHandle { + if let AocFile::Directory(files) = self { + let handle = files.entry(dir.to_string()).or_default().clone(); + + // debug!("Entering directory: {:?}", handle); + return handle; + } + + panic!("cannot cd in file") + } + + fn size_under_100000(&self) -> (bool, usize, usize) { + match self { + AocFile::File(s) => (false, 0, *s), + AocFile::Directory(files) => { + let (running_total, size) = files + .values() + .map(|f| f.borrow().size_under_100000()) + .fold((0_usize, 0_usize), |(mut running, size), (dir, r, s)| { + // debug!("Checking ({}, {}, {})", dir, r, s); + + if dir && s <= 100000 { + // debug!("Is small (hehe) directory, adding to total: {s}"); + running += s; + } + + (r + running, size + s) + }); + + (true, running_total, size) + } + } + } + + fn smallest_bigger(&self, required: usize) -> (bool, usize, usize) { + match self { + AocFile::File(s) => (false, usize::MAX, *s), + AocFile::Directory(files) => { + let (mut dir_min, size) = files + .values() + .map(|f| f.borrow().smallest_bigger(required)) + .fold( + (usize::MAX, 0_usize), + |(mut current_min, size), (dir, r_min, s)| { + if dir && s >= required { + // we got back from a subdirectory + current_min = min(current_min, s); + } + + // also check local solutions in subdirectories + (min(current_min, r_min), size + s) + }, + ); + + if size >= required { + dir_min = min(dir_min, size); + } + + (true, dir_min, size) + } + } + } +} + +struct Filesystem { + root: FileHandle, + cwd: Vec, +} + +impl Filesystem { + fn new() -> Filesystem { + let root = AocFile::dir(); + + Filesystem { + root: root.clone(), + cwd: vec![root], + } + } + + fn cd(&mut self, dir: &str) { + // debug!("swish swish: {}", dir); + + match dir { + ".." => { + self.cwd.pop(); + } + "/" => { + self.cwd.clear(); + self.cwd.push(self.root.clone()); + } + _ => { + let idx = self.cwd.len() - 1; + let subdir = self.cwd[idx].borrow_mut().cd(dir); + self.cwd.push(subdir); + } + } + } + + fn touch(&mut self, name: &str, size: usize) { + let idx = self.cwd.len() - 1; + // debug!("touchy touchy: {} ({})", name, size); + self.cwd[idx].borrow_mut().add_file(name, size); + } + + fn run_command(&mut self, command: &str) { + if command.starts_with("cd ") { + let parts = command.split_ascii_whitespace().collect_vec(); + self.cd(parts[1]); + return; + } + + for file in command.lines().skip(1) { + let parts = file.split_ascii_whitespace().collect_vec(); + if parts[0] == "dir" { + /* no-op */ + } else { + let name = parts[1]; + let size: usize = parts[0].parse().unwrap(); + self.touch(name, size); + } + } + } +} + +impl FromStr for Filesystem { + type Err = Report; + + fn from_str(s: &str) -> Result { + let mut fs = Filesystem::new(); + + for command in s.trim_start_matches("$ ").split("\n$ ") { + // debug!("Running command: {}", command); + fs.run_command(command); + } + + Ok(fs) + } +} + +type Input = Filesystem; +type Output = usize; + +fn part_1(input: &Input) -> Output { + input.root.borrow().size_under_100000().1 +} + +fn part_2(input: &Input) -> Output { + let (total, needed) = (70000000, 30000000); + let used = input.root.borrow().size_under_100000().2; + // to_be_freed >= needed - (total - used) + let to_be_freed = needed - (total - used); + + // debug!( + // "Used: {}, free: {}, to_be_freed: {}", + // used, + // total - used, + // to_be_freed + // ); + + input.root.borrow().smallest_bigger(to_be_freed).1 +} + +fn parse_input(pathname: &str) -> Input { + file_to_string(pathname).parse().unwrap() +} + +fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_target(false) + .with_file(true) + .with_line_number(true) + .without_time() + .compact() + .init(); + color_eyre::install()?; + + let input = parse_input("inputs/day07.txt"); + + info!("Part 1: {}", part_1(&input)); + info!("Part 2: {}", part_2(&input)); + + Ok(()) +} + +#[cfg(test)] +mod day_06 { + use super::*; + + #[test] + fn test_part_1() { + let sample = parse_input("samples/day07.txt"); + assert_eq!(part_1(&sample), 95437); + } + + #[test] + fn test_part_2() { + let sample = parse_input("samples/day07.txt"); + assert_eq!(part_2(&sample), 24933642); + } +}