day07 finished
This commit is contained in:
parent
5a6abbd73c
commit
745abc936e
4 changed files with 1502 additions and 1 deletions
341
src/days/day07/mod.rs
Normal file
341
src/days/day07/mod.rs
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
num::ParseIntError,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
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: &[String]) -> anyhow::Result<ResultType> {
|
||||
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: &[String]) -> anyhow::Result<ResultType> {
|
||||
const AVAIL: i64 = 70_000_000;
|
||||
const REQUIRED: i64 = 30_000_000;
|
||||
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: String,
|
||||
sub_dirs: Vec<Rc<RefCell<Node>>>,
|
||||
file_size: i64,
|
||||
size: Cell<Option<i64>>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn root() -> Node {
|
||||
Node {
|
||||
parent: None,
|
||||
name: "/".to_owned(),
|
||||
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(&mut self, _name: &str, 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(&mut self, name: &str) {
|
||||
let subdir = Rc::new(RefCell::new(Node {
|
||||
parent: Some(Rc::downgrade(&self.node)),
|
||||
name: name.to_owned(),
|
||||
sub_dirs: vec![],
|
||||
file_size: 0,
|
||||
size: Cell::new(None),
|
||||
}));
|
||||
self.node.borrow_mut().add_subdir(subdir);
|
||||
}
|
||||
|
||||
pub fn add_file(&mut self, name: &str, 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(&self, name: &str) -> Option<Directory> {
|
||||
let node = self.node.borrow();
|
||||
let sub_node = node.sub_dirs.iter().find(|node| node.borrow().name == name);
|
||||
sub_node.map(|node| Directory::create(node.clone()))
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> 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_lines;
|
||||
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_lines(day.get_day_number(), "example01.txt")?;
|
||||
let dir = Directory::parse(&lines)?;
|
||||
assert_eq!(dir.size(), 48381165);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterator() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
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_lines(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_lines(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(24933642);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue