advent-2022-rust/src/days/day07/mod.rs

346 lines
9.6 KiB
Rust

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<ResultType> {
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<ResultType> {
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<Weak<RefCell<Node>>>,
name: Name,
sub_dirs: Vec<Rc<RefCell<Node>>>,
file_size: i64,
size: Cell<Option<i64>>,
}
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<RefCell<Node>>) {
self.sub_dirs.push(subdir);
}
fn add_file<N: Into<Name>>(&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<RefCell<Node>>,
}
impl Directory {
pub fn root() -> Directory {
Directory {
node: Rc::new(RefCell::new(Node::root())),
}
}
fn create(node: Rc<RefCell<Node>>) -> 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<N: Into<Name>>(&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<N: Into<Name>>(&mut self, name: N, size: i64) {
self.node.borrow_mut().add_file(name, size)
}
pub fn parent(&self) -> Option<Directory> {
self.node
.borrow()
.parent
.as_ref()
.and_then(|node| node.upgrade())
.map(|node| Directory { node })
}
pub fn get_subdir<N: Into<Name>>(&self, name: N) -> Option<Directory> {
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<Item = &'a str>) -> Result<Directory, DirectoryError> {
let root = Directory::root();
let mut current = root.clone();
for line in lines {
match line.split_whitespace().collect::<Vec<&str>>()[..] {
["$", "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::<i64>()?;
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<RefCell<Node>>,
subdirectory: Option<(usize, Box<DirectoryIterator>)>,
finished: bool,
}
impl DirectoryIterator {
pub fn create(directory: Rc<RefCell<Node>>) -> DirectoryIterator {
DirectoryIterator {
directory,
subdirectory: None,
finished: false,
}
}
}
impl Iterator for DirectoryIterator {
type Item = Directory;
fn next(&mut self) -> Option<Self::Item> {
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(())
}
}