use super::template::{DayTrait, ResultType}; use crate::common::{file::split_lines, name::Name}; use std::{ cell::{Cell, RefCell}, num::ParseIntError, rc::{Rc, Weak}, }; use thiserror::Error; const DAY_NUMBER: usize = 7; pub struct Day; impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } fn part1(&self, lines: &str) -> anyhow::Result { let lines = split_lines(lines); let root = Directory::parse(lines)?; let result = root .iter() .filter_map(|dir| { let size = dir.size(); if size <= 100000 { Some(size) } else { None } }) .sum(); Ok(ResultType::Integer(result)) } fn part2(&self, lines: &str) -> anyhow::Result { const AVAIL: i64 = 70_000_000; const REQUIRED: i64 = 30_000_000; let lines = split_lines(lines); let root = Directory::parse(lines)?; let to_free = REQUIRED - (AVAIL - root.size()); let result = root .iter() .filter_map(|dir| { let size = dir.size(); if size >= to_free { Some(size) } else { None } }) .min() .unwrap_or(0); Ok(ResultType::Integer(result)) } } #[derive(Debug, Error)] enum DirectoryError { #[error("Directory does not exist: {0}")] NoSuchDirectory(String), #[error("Directory has no parent")] NoParentDirectory, #[error("Illegal command: {0}")] IllegalCommand(String), #[error("No a legal Number")] IllegalNumber(#[from] ParseIntError), } #[derive(Debug)] struct Node { parent: Option>>, name: Name, sub_dirs: Vec>>, file_size: i64, size: Cell>, } impl Node { fn root() -> Node { Node { parent: None, name: "/".into(), sub_dirs: vec![], file_size: 0, size: Cell::new(None), } } fn add_subdir(&mut self, subdir: Rc>) { self.sub_dirs.push(subdir); } fn add_file>(&mut self, _name: N, size: i64) { self.file_size += size; } fn size(&self) -> i64 { self.size.get().unwrap_or_else(|| { let subsize: i64 = self.sub_dirs.iter().map(|sub| sub.borrow().size()).sum(); let size = subsize + self.file_size; self.size.set(Some(size)); size }) } } #[derive(Debug, Clone)] struct Directory { node: Rc>, } impl Directory { pub fn root() -> Directory { Directory { node: Rc::new(RefCell::new(Node::root())), } } fn create(node: Rc>) -> Directory { Directory { node } } #[allow(dead_code)] pub fn name(&self) -> String { self.node.borrow().name.to_string() } pub fn size(&self) -> i64 { self.node.borrow().size() } #[allow(dead_code)] pub fn file_size(&self) -> i64 { self.node.borrow().file_size } pub fn add_subdir>(&mut self, name: N) { let subdir = Rc::new(RefCell::new(Node { parent: Some(Rc::downgrade(&self.node)), name: name.into(), sub_dirs: vec![], file_size: 0, size: Cell::new(None), })); self.node.borrow_mut().add_subdir(subdir); } pub fn add_file>(&mut self, name: N, size: i64) { self.node.borrow_mut().add_file(name, size) } pub fn parent(&self) -> Option { self.node .borrow() .parent .as_ref() .and_then(|node| node.upgrade()) .map(|node| Directory { node }) } pub fn get_subdir>(&self, name: N) -> Option { let node = self.node.borrow(); let name = name.into(); let sub_node = node.sub_dirs.iter().find(|node| node.borrow().name == name); sub_node.map(|node| Directory::create(node.clone())) } pub fn parse<'a>(lines: impl Iterator) -> Result { let root = Directory::root(); let mut current = root.clone(); for line in lines { match line.split_whitespace().collect::>()[..] { ["$", "cd", dir] => { current = match dir { "/" => root.clone(), ".." => { let Some(next) = current.parent() else { return Err(DirectoryError::NoParentDirectory); }; next } _ => { let Some(next) = current.get_subdir(dir) else { return Err(DirectoryError::NoSuchDirectory(dir.to_owned())); }; next } }; } ["$", "ls"] => {} // Command can safely be ignored ["dir", name] => { current.add_subdir(name); } [size, name] => { let size = size.parse::()?; current.add_file(name, size); } _ => return Err(DirectoryError::IllegalCommand(line.to_owned())), } } Ok(root) } pub fn iter(&self) -> DirectoryIterator { DirectoryIterator::create(self.node.clone()) } } #[derive(Debug)] struct DirectoryIterator { directory: Rc>, subdirectory: Option<(usize, Box)>, finished: bool, } impl DirectoryIterator { pub fn create(directory: Rc>) -> DirectoryIterator { DirectoryIterator { directory, subdirectory: None, finished: false, } } } impl Iterator for DirectoryIterator { type Item = Directory; fn next(&mut self) -> Option { if self.finished { return None; } if let Some((subdirectory, sub_iterator)) = self.subdirectory.as_mut() { if let Some(next) = sub_iterator.next() { Some(next) } else { let dir = self.directory.borrow(); let subdirectory = *subdirectory + 1; if subdirectory < dir.sub_dirs.len() { let subdir = dir.sub_dirs[subdirectory].clone(); let mut sub_iterator = DirectoryIterator::create(subdir); let result = sub_iterator.next(); self.subdirectory = Some((subdirectory, Box::new(sub_iterator))); result } else { self.finished = true; None } } } else { let dir = self.directory.borrow(); if !dir.sub_dirs.is_empty() { let subdir = dir.sub_dirs[0].clone(); let sub_iterator = DirectoryIterator::create(subdir); self.subdirectory = Some((0, Box::new(sub_iterator))); } else { self.finished = true; } Some(Directory { node: self.directory.clone(), }) } } } #[cfg(test)] mod test { use super::*; use crate::common::file::read_string; use anyhow::Result; #[test] fn test_create_directory() { let directory = Directory::root(); assert_eq!(directory.name(), "/"); assert_eq!(directory.size(), 0); } #[test] fn test_create_and_get_subdir() { let mut root = Directory::root(); root.add_file("file1", 100); root.add_file("file2", 200); root.add_subdir("subdir"); let mut subdir = root.get_subdir("subdir").unwrap(); subdir.add_file("file3", 300); subdir.add_file("file4", 400); assert_eq!(subdir.name(), "subdir"); assert_eq!(subdir.size(), 700); assert_eq!(root.size(), 1000); assert_eq!(root.file_size(), 300); } #[test] fn test_total_size() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let lines = split_lines(&lines); let dir = Directory::parse(lines)?; assert_eq!(dir.size(), 48381165); Ok(()) } #[test] fn test_iterator() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let lines = split_lines(&lines); let expected = vec!["/", "a", "e", "d"]; let dir = Directory::parse(lines)?; let result: Vec<_> = dir.iter().map(|dir| dir.name()).collect(); assert_eq!(result, expected); Ok(()) } #[test] fn test_part1() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(95437); let result = day.part1(&lines)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_part2() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(24933642); let result = day.part2(&lines)?; assert_eq!(result, expected); Ok(()) } }