switched to workspaces.

This should let me make a cross-day library.
This commit is contained in:
Gabe Venberg 2023-11-19 20:32:41 -06:00
parent 1469c3a32b
commit 242989bb95
57 changed files with 156 additions and 89 deletions

11
days/day07/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "day07"
authors.workspace = true
description.workspace = true
version.workspace = true
edition.workspace = true
[dependencies]
once_cell.workspace = true
regex.workspace = true
thiserror.workspace = true

305
days/day07/src/file_tree.rs Normal file
View file

@ -0,0 +1,305 @@
use std::{
cell::RefCell,
fmt::Display,
ops::Deref,
rc::{Rc, Weak},
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FileTreeError {
#[error("Directory operation on file")]
IsFile,
#[error("File operation on directory")]
IsDir,
#[error("File not found")]
FileNotFound,
#[error("File already exists")]
FileAlreadyExists,
}
type WeakNodeRef = Weak<RefCell<Node>>;
#[derive(Debug)]
pub struct NodeRef(pub Rc<RefCell<Node>>);
impl Deref for NodeRef {
type Target = Rc<RefCell<Node>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Rc<RefCell<Node>>> for NodeRef {
fn from(value: Rc<RefCell<Node>>) -> Self {
NodeRef(value)
}
}
impl From<NodeRef> for Rc<RefCell<Node>> {
fn from(value: NodeRef) -> Self {
value.0
}
}
impl NodeRef {
pub fn add_node(&mut self, mut node: Node) -> Result<NodeRef, FileTreeError> {
node.set_parent(self);
self.borrow_mut().add_children(node)
}
pub fn new_dir(name: String) -> NodeRef {
NodeRef(Rc::new(RefCell::new(Node::new_dir(name))))
}
pub fn add_file(&mut self, name: String, size: usize) -> Result<NodeRef, FileTreeError> {
self.add_node(Node::new_file(name, size))
}
pub fn add_dir(&mut self, name: String) -> Result<NodeRef, FileTreeError> {
self.add_node(Node::new_dir(name))
}
pub fn get_all_dirs(&self) -> Vec<NodeRef> {
let mut ret = Vec::new();
match &self.borrow().contents {
Contents::Size(_) => {}
Contents::Children(c) => {
ret.push(NodeRef(Rc::clone(self)));
for node in c {
ret.append(&mut node.get_all_dirs())
}
}
};
ret
}
}
#[derive(Debug)]
pub struct Node {
pub name: String,
parent: Option<WeakNodeRef>,
contents: Contents,
}
impl Node {
pub fn new_dir(name: String) -> Node {
Node {
name,
parent: None,
contents: Contents::Children(Vec::new()),
}
}
pub fn new_file(name: String, size: usize) -> Node {
Node {
name,
parent: None,
contents: Contents::Size(size),
}
}
pub fn get_total_size(&self) -> usize {
match &self.contents {
Contents::Size(s) => *s,
Contents::Children(c) => c.iter().map(|f| f.borrow().get_total_size()).sum(),
}
}
fn set_parent(&mut self, newparent: &NodeRef) {
self.parent = Some(Rc::downgrade(newparent));
}
// does not set the parent, needs to be done on the NodeRef. (this is why this func isnt pub).
// takes onwership of the node to make sure its not owned by another tree.
fn add_children(&mut self, node: Node) -> Result<NodeRef, FileTreeError> {
match self.contents {
Contents::Size(_) => Err(FileTreeError::IsFile),
Contents::Children(ref mut c) => {
for file in c.iter() {
if file.borrow().name == node.name {
return Err(FileTreeError::FileAlreadyExists);
}
}
let rc = Rc::new(RefCell::new(node));
c.push(NodeRef(Rc::clone(&rc)));
Ok(NodeRef(Rc::clone(&rc)))
}
}
}
pub fn remove_child_by_name(&mut self, name: &str) -> Result<(), FileTreeError> {
match self.contents {
Contents::Size(_) => Err(FileTreeError::IsFile),
Contents::Children(ref mut c) => {
for (i, file) in c.iter().enumerate() {
if file.borrow().name == name {
c.remove(i);
return Ok(());
}
}
Err(FileTreeError::FileNotFound)
}
}
}
pub fn get_parent(&self) -> Option<NodeRef> {
match &self.parent {
Some(w) => Some(NodeRef(w.clone().upgrade()?)),
None => None,
}
}
pub fn get_size(&self) -> Result<usize, FileTreeError> {
match self.contents {
Contents::Size(s) => Ok(s),
Contents::Children(_) => Err(FileTreeError::IsDir),
}
}
pub fn set_size(&mut self, size: usize) -> Result<(), FileTreeError> {
match self.contents {
Contents::Size(ref mut s) => {
*s = size;
Ok(())
}
Contents::Children(_) => Err(FileTreeError::IsDir),
}
}
pub fn get_children(&self) -> Result<impl Iterator<Item = NodeRef> + '_, FileTreeError> {
match &self.contents {
Contents::Size(_) => Err(FileTreeError::IsFile),
Contents::Children(c) => Ok(c.iter().map(|n| NodeRef(Rc::clone(n)))),
}
}
pub fn get_child_by_name(&self, name: &str) -> Result<NodeRef, FileTreeError> {
match &self.contents {
Contents::Size(_) => Err(FileTreeError::IsFile),
Contents::Children(c) => {
for file in c.iter() {
if file.borrow().name == name {
return Ok(NodeRef(Rc::clone(file)));
}
}
Err(FileTreeError::FileNotFound)
}
}
}
}
#[derive(Debug)]
enum Contents {
Size(usize),
Children(Vec<NodeRef>),
}
impl Display for NodeRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.borrow())
}
}
impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "- {} {}", self.name, self.contents)
}
}
impl Display for Contents {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Contents::Size(s) => write!(f, "(file, size = {})", s),
Contents::Children(c) => {
writeln!(f, "(dir)").expect("I have no clue how this could fail");
for node in c {
//padding
for line in format!("{}", node).lines() {
writeln!(f, " {line}")?;
}
}
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dir_construction() {
let mut root = NodeRef::new_dir("/".to_string());
let mut cursor = root.add_node(Node::new_dir("a".to_string())).unwrap();
cursor = cursor.add_dir("e".to_string()).unwrap();
cursor.add_file("i".to_string(), 584).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("f".to_string(), 29116).unwrap();
cursor.add_file("g".to_string(), 2557).unwrap();
cursor.add_file("h.lst".to_string(), 62596).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("b.txt".to_string(), 14848514).unwrap();
cursor.add_file("c.dat".to_string(), 8504156).unwrap();
cursor = cursor.add_dir("d".to_string()).unwrap();
cursor.add_file("j".to_string(), 4060174).unwrap();
cursor.add_file("d.log".to_string(), 8033020).unwrap();
cursor.add_file("d.ext".to_string(), 5626152).unwrap();
cursor.add_file("k".to_string(), 7214296).unwrap();
assert_eq!(Rc::clone(root.deref()).borrow().get_total_size(), 48381165);
println!("{}", root);
}
#[test]
fn test_size_calcs() {
let mut root = NodeRef::new_dir("/".to_string());
let mut dirs: Vec<NodeRef> = Vec::new();
let mut cursor = root.add_dir("a".to_string()).unwrap();
dirs.push(NodeRef(Rc::clone(&cursor)));
cursor = cursor.add_dir("e".to_string()).unwrap();
dirs.push(NodeRef(Rc::clone(&cursor)));
cursor.add_file("i".to_string(), 584).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("f".to_string(), 29116).unwrap();
cursor.add_file("g".to_string(), 2557).unwrap();
cursor.add_file("h.lst".to_string(), 62596).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("b.txt".to_string(), 14848514).unwrap();
cursor.add_file("c.dat".to_string(), 8504156).unwrap();
cursor = cursor.add_node(Node::new_dir("d".to_string())).unwrap();
dirs.push(NodeRef(Rc::clone(&cursor)));
cursor.add_file("j".to_string(), 4060174).unwrap();
cursor.add_file("d.log".to_string(), 8033020).unwrap();
cursor.add_file("d.ext".to_string(), 5626152).unwrap();
cursor.add_file("k".to_string(), 7214296).unwrap();
assert_eq!(Rc::clone(root.deref()).borrow().get_total_size(), 48381165);
assert_eq!(Rc::clone(dirs[0].deref()).borrow().get_total_size(), 94853);
assert_eq!(Rc::clone(dirs[1].deref()).borrow().get_total_size(), 584);
assert_eq!(
Rc::clone(dirs[2].deref()).borrow().get_total_size(),
24933642
);
}
#[test]
fn test_get_all_dirs() {
let mut root = NodeRef::new_dir("/".to_string());
let mut cursor = root.add_dir("a".to_string()).unwrap();
cursor = cursor.add_dir("e".to_string()).unwrap();
cursor.add_file("i".to_string(), 584).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("f".to_string(), 29116).unwrap();
cursor.add_file("g".to_string(), 2557).unwrap();
cursor.add_file("h.lst".to_string(), 62596).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("b.txt".to_string(), 14848514).unwrap();
cursor.add_file("c.dat".to_string(), 8504156).unwrap();
cursor = cursor.add_node(Node::new_dir("d".to_string())).unwrap();
cursor.add_file("j".to_string(), 4060174).unwrap();
cursor.add_file("d.log".to_string(), 8033020).unwrap();
cursor.add_file("d.ext".to_string(), 5626152).unwrap();
cursor.add_file("k".to_string(), 7214296).unwrap();
let dirs = root.get_all_dirs();
let sizes: Vec<usize> = dirs.iter().map(|d| d.borrow().get_total_size()).collect();
println!("{:?}", sizes);
assert_eq!(Rc::clone(root.deref()).borrow().get_total_size(), 48381165);
assert_eq!(
Rc::clone(dirs[0].deref()).borrow().get_total_size(),
48381165
);
assert_eq!(Rc::clone(dirs[1].deref()).borrow().get_total_size(), 94853);
assert_eq!(Rc::clone(dirs[2].deref()).borrow().get_total_size(), 584);
assert_eq!(
Rc::clone(dirs[3].deref()).borrow().get_total_size(),
24933642
);
}
}

1030
days/day07/src/input.txt Normal file

File diff suppressed because it is too large Load diff

19
days/day07/src/main.rs Normal file
View file

@ -0,0 +1,19 @@
use std::rc::Rc;
use crate::file_tree::NodeRef;
mod part1;
mod part2;
mod parser;
mod file_tree;
fn main() {
let input = include_str!("./input.txt");
let structured_input = parser::parse(input);
println!("Part One");
println!("Result: {}", part1::part1(NodeRef(Rc::clone(&structured_input))));
println!("Part Two");
println!("Result: {}", part2::part2(NodeRef(Rc::clone(&structured_input))));
}

262
days/day07/src/parser.rs Normal file
View file

@ -0,0 +1,262 @@
use std::ops::Deref;
use std::rc::Rc;
use crate::file_tree::*;
use once_cell::sync::Lazy;
use regex::Regex;
static IS_COMMAND_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\$").unwrap());
static PARSE_CD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\$ cd (\S*)$").unwrap());
static PARSE_LS_ENTRY_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^([[:alnum:]]*) (\S*)$").unwrap());
#[derive(Debug, PartialEq, Eq)]
pub enum Command {
CdRoot,
CdUp,
Cd(String),
Ls(Vec<LsEntry>),
}
#[derive(Debug, PartialEq, Eq)]
pub enum LsEntry {
Dir(String),
File(ParseFile),
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseFile {
size: usize,
name: String,
}
//parses a single line
pub fn parse_to_commands(input: &str) -> Vec<Command> {
let mut ret = Vec::new();
let mut lines = input.lines().peekable();
while lines.peek().is_some() {
let line = lines.next().unwrap();
if line == "$ ls" {
ret.push(Command::Ls(parse_ls(&mut lines)));
} else {
let captures = PARSE_CD_REGEX
.captures(line)
.unwrap_or_else(|| panic!("invalid line {}", line));
ret.push(match &captures[1] {
".." => Command::CdUp,
"/" => Command::CdRoot,
s => Command::Cd(s.to_string()),
})
}
}
ret
}
fn parse_ls(lines: &mut std::iter::Peekable<std::str::Lines<'_>>) -> Vec<LsEntry> {
let mut ret: Vec<LsEntry> = Vec::new();
while lines.peek().is_some() {
// if the next line is a command, then we are at the end of the ls listing.
let line = lines.peek().expect("no next line");
if IS_COMMAND_REGEX.is_match(line) {
break;
}
let captures = PARSE_LS_ENTRY_REGEX
.captures(line)
.unwrap_or_else(|| panic!("invalid line {}", line));
ret.push(match &captures[1] {
"dir" => LsEntry::Dir(captures[2].to_string()),
_ => LsEntry::File(ParseFile {
size: str::parse(&captures[1]).unwrap_or_else(|_| panic!("invalid line {}", line)),
name: captures[2].to_string(),
}),
});
lines.next();
}
ret
}
pub fn parse(input: &str) -> NodeRef {
commands_to_tree(parse_to_commands(input))
}
pub fn commands_to_tree(input: Vec<Command>) -> NodeRef {
let root = NodeRef::new_dir("/".to_string());
let mut cursor = NodeRef(Rc::clone(&root));
for command in input {
match command {
Command::CdRoot => cursor = NodeRef(Rc::clone(&root)),
Command::CdUp => cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap(),
Command::Cd(name) => cursor = cursor.add_dir(name).unwrap(),
Command::Ls(ls) => {
for entry in ls {
match entry {
LsEntry::Dir(_) => {
//dirs dont exist until you cd into them.
}
LsEntry::File(f) => {
cursor.add_file(f.name, f.size).unwrap();
}
};
}
}
}
}
root
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_to_commands() {
let input = concat!(
"$ cd /\n",
"$ ls\n",
"dir a\n",
"14848514 b.txt\n",
"8504156 c.dat\n",
"dir d\n",
"$ cd a\n",
"$ ls\n",
"dir e\n",
"29116 f\n",
"2557 g\n",
"62596 h.lst\n",
"$ cd e\n",
"$ ls\n",
"584 i\n",
"$ cd ..\n",
"$ cd ..\n",
"$ cd d\n",
"$ ls\n",
"4060174 j\n",
"8033020 d.log\n",
"5626152 d.ext\n",
"7214296 k"
);
assert_eq!(
parse_to_commands(input),
vec![
Command::CdRoot,
Command::Ls(vec![
LsEntry::Dir(String::from("a")),
LsEntry::File(ParseFile {
size: 14848514,
name: String::from("b.txt")
}),
LsEntry::File(ParseFile {
size: 8504156,
name: String::from("c.dat")
}),
LsEntry::Dir(String::from("d"))
]),
Command::Cd(String::from("a")),
Command::Ls(vec![
LsEntry::Dir(String::from("e")),
LsEntry::File(ParseFile {
size: 29116,
name: String::from("f")
}),
LsEntry::File(ParseFile {
size: 2557,
name: String::from("g")
}),
LsEntry::File(ParseFile {
size: 62596,
name: String::from("h.lst")
}),
]),
Command::Cd(String::from("e")),
Command::Ls(vec![LsEntry::File(ParseFile {
size: 584,
name: String::from("i")
}),]),
Command::CdUp,
Command::CdUp,
Command::Cd(String::from("d")),
Command::Ls(vec![
LsEntry::File(ParseFile {
size: 4060174,
name: String::from("j")
}),
LsEntry::File(ParseFile {
size: 8033020,
name: String::from("d.log")
}),
LsEntry::File(ParseFile {
size: 5626152,
name: String::from("d.ext")
}),
LsEntry::File(ParseFile {
size: 7214296,
name: String::from("k")
}),
]),
]
)
}
#[test]
fn test_commands_to_tree() {
let input = vec![
Command::CdRoot,
Command::Ls(vec![
LsEntry::Dir(String::from("a")),
LsEntry::File(ParseFile {
size: 14848514,
name: String::from("b.txt"),
}),
LsEntry::File(ParseFile {
size: 8504156,
name: String::from("c.dat"),
}),
LsEntry::Dir(String::from("d")),
]),
Command::Cd(String::from("a")),
Command::Ls(vec![
LsEntry::Dir(String::from("e")),
LsEntry::File(ParseFile {
size: 29116,
name: String::from("f"),
}),
LsEntry::File(ParseFile {
size: 2557,
name: String::from("g"),
}),
LsEntry::File(ParseFile {
size: 62596,
name: String::from("h.lst"),
}),
]),
Command::Cd(String::from("e")),
Command::Ls(vec![LsEntry::File(ParseFile {
size: 584,
name: String::from("i"),
})]),
Command::CdUp,
Command::CdUp,
Command::Cd(String::from("d")),
Command::Ls(vec![
LsEntry::File(ParseFile {
size: 4060174,
name: String::from("j"),
}),
LsEntry::File(ParseFile {
size: 8033020,
name: String::from("d.log"),
}),
LsEntry::File(ParseFile {
size: 5626152,
name: String::from("d.ext"),
}),
LsEntry::File(ParseFile {
size: 7214296,
name: String::from("k"),
}),
]),
];
let tree = commands_to_tree(input);
println!("{}", tree);
assert_eq!(Rc::clone(tree.deref()).borrow().get_total_size(), 48381165)
}
}

37
days/day07/src/part1.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::file_tree::*;
pub fn part1(input: NodeRef) -> usize {
let dirs = input.get_all_dirs();
dirs.iter()
.map(|d| d.borrow().get_total_size())
.filter(|s| *s <= 100000)
.sum()
}
#[cfg(test)]
mod tests {
use std::{ops::Deref, rc::Rc};
use super::*;
#[test]
fn test_part1() {
let mut root = NodeRef::new_dir("/".to_string());
let mut cursor = root.add_node(Node::new_dir("a".to_string())).unwrap();
cursor = cursor.add_dir("e".to_string()).unwrap();
cursor.add_file("i".to_string(), 584).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("f".to_string(), 29116).unwrap();
cursor.add_file("g".to_string(), 2557).unwrap();
cursor.add_file("h.lst".to_string(), 62596).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("b.txt".to_string(), 14848514).unwrap();
cursor.add_file("c.dat".to_string(), 8504156).unwrap();
cursor = cursor.add_dir("d".to_string()).unwrap();
cursor.add_file("j".to_string(), 4060174).unwrap();
cursor.add_file("d.log".to_string(), 8033020).unwrap();
cursor.add_file("d.ext".to_string(), 5626152).unwrap();
cursor.add_file("k".to_string(), 7214296).unwrap();
assert_eq!(part1(root), 95437);
}
}

44
days/day07/src/part2.rs Normal file
View file

@ -0,0 +1,44 @@
use crate::file_tree::*;
const TOTAL_SPACE: usize = 70000000;
const NEEDED_SPACE: usize = 30000000;
pub fn part2(input: NodeRef) -> usize {
let used_space = input.borrow().get_total_size();
let unused_space = TOTAL_SPACE - used_space;
let space_to_free = NEEDED_SPACE - unused_space;
let dirs = input.get_all_dirs();
dirs.iter()
.map(|d| d.borrow().get_total_size())
.filter(|s| *s >= space_to_free)
.min()
.unwrap()
}
#[cfg(test)]
mod tests {
use std::{ops::Deref, rc::Rc};
use super::*;
#[test]
fn test_part2() {
let mut root = NodeRef::new_dir("/".to_string());
let mut cursor = root.add_node(Node::new_dir("a".to_string())).unwrap();
cursor = cursor.add_dir("e".to_string()).unwrap();
cursor.add_file("i".to_string(), 584).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("f".to_string(), 29116).unwrap();
cursor.add_file("g".to_string(), 2557).unwrap();
cursor.add_file("h.lst".to_string(), 62596).unwrap();
cursor = Rc::clone(cursor.deref()).borrow().get_parent().unwrap();
cursor.add_file("b.txt".to_string(), 14848514).unwrap();
cursor.add_file("c.dat".to_string(), 8504156).unwrap();
cursor = cursor.add_dir("d".to_string()).unwrap();
cursor.add_file("j".to_string(), 4060174).unwrap();
cursor.add_file("d.log".to_string(), 8033020).unwrap();
cursor.add_file("d.ext".to_string(), 5626152).unwrap();
cursor.add_file("k".to_string(), 7214296).unwrap();
assert_eq!(part2(root), 24933642);
}
}