/// --- Part Two ---
///
/// Now, you're ready to choose a directory to delete.
///
/// The total disk space available to the filesystem is 70000000. To run the update, you need
/// unused space of at least 30000000. You need to find a directory you can delete that will free
/// up enough space to run the update.
///
/// In the example above, the total size of the outermost directory (and thus the total amount of
/// used space) is 48381165; this means that the size of the unused space must currently be
/// 21618835, which isn't quite the 30000000 required by the update. Therefore, the update still
/// requires a directory with total size of at least 8381165 to be deleted before it can run.
///
/// To achieve this, you have the following options:
///
/// Delete directory e, which would increase unused space by 584.
/// Delete directory a, which would increase unused space by 94853.
/// Delete directory d, which would increase unused space by 24933642.
/// Delete directory /, which would increase unused space by 48381165.
///
/// Directories e and a are both too small; deleting them would not free up enough space. However,
/// directories d and / are both big enough! Between these, choose the smallest: d, increasing
/// unused space by 24933642.
///
/// Find the smallest directory that, if deleted, would free up enough space on the filesystem to
/// run the update. What is the total size of that directory?
use clap::Parser;
use nom::branch::alt;
use nom::bytes::streaming::tag;
use nom::character::streaming::{char, newline, not_line_ending, space1, u64};
use nom::combinator::{map, peek};
use nom::error::{context, ContextError, ErrorKind as NomErrorKind, ParseError};
use nom::multi::many_till;
use nom::sequence::{delimited, preceded, terminated, tuple};
use nom::IResult;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::iter::*;
use std::path::PathBuf;
pub type Input<'a> = &'a str;
pub type Result<'a, T> = IResult, T, Error>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ErrorKind {
Nom(NomErrorKind),
Context(&'static str),
Custom(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub errors: Vec<(I, ErrorKind)>,
}
impl ParseError for Error {
fn from_error_kind(input: I, kind: NomErrorKind) -> Self {
let errors = vec![(input, ErrorKind::Nom(kind))];
Self { errors }
}
fn append(input: I, kind: NomErrorKind, mut other: Self) -> Self {
other.errors.push((input, ErrorKind::Nom(kind)));
other
}
}
impl ContextError for Error {
fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
other.errors.push((input, ErrorKind::Context(ctx)));
other
}
}
const FILEPATH: &'static str = "examples/input.txt";
const EOF_CHAR: char = '\0';
const TOTALSPACE: u64 = 70000000;
const NEEDEDSPACE: u64 = 30000000;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(short, long, default_value = FILEPATH)]
file: PathBuf,
}
#[derive(Clone, Debug)]
enum Commands {
ChangeDir(String),
List(Vec),
}
#[derive(Clone, Debug)]
enum ListEntry {
File(FileEntry),
Folder(String),
}
#[derive(Clone, Debug)]
struct FileEntry {
name: String,
size: u64,
}
#[derive(Clone, Debug)]
struct Arena(Vec);
#[derive(Clone, Debug)]
struct Node {
size: u64,
parent: Option,
children: HashMap,
}
impl Node {
fn new() -> Self {
Node {
size: 0,
parent: None,
children: HashMap::new(),
}
}
fn with_parent(idx: usize) -> Self {
Node {
size: 0,
parent: Some(idx),
children: HashMap::new(),
}
}
}
impl Arena {
// NOTE: only call this once per node
fn consume(&mut self, idx: usize, ls: Vec) {
for le in ls.into_iter() {
match le {
ListEntry::File(f) => {
let mut iteridx = idx;
loop {
self.0[iteridx].size += f.size;
iteridx = if let Some(pidx) = self.0[iteridx].parent {
pidx
} else {
break;
};
}
}
ListEntry::Folder(f) => {
let fidx = self.0.len();
self.0.push(Node::with_parent(idx));
self.0[idx].children.insert(f, fidx);
}
}
}
}
fn get(&mut self, idx: usize) -> &mut Node {
&mut self.0[idx]
}
}
fn parse_cd(input: &str) -> Result {
context(
"parse_cd",
map(
delimited(tuple((tag("cd"), space1)), not_line_ending, newline),
|s: &str| Commands::ChangeDir(s.to_string()),
),
)(input)
}
fn end_of_file_or_ls(input: &str) -> Result {
alt((char(EOF_CHAR), peek(char('$'))))(input)
}
fn parse_ls(input: &str) -> Result {
let (input, _) = context("parse_ls", terminated(tag("ls"), newline))(input)?;
let (input, (lsv, _)) = many_till(parse_ls_entry, end_of_file_or_ls)(input)?;
Ok((input, Commands::List(lsv)))
}
fn parse_ls_entry(input: &str) -> Result {
context("parse_ls_entry", alt((parse_file, parse_folder)))(input)
}
fn parse_file(input: &str) -> Result {
map(
terminated(tuple((u64, preceded(tag(" "), not_line_ending))), newline),
|(size, name): (u64, &str)| {
ListEntry::File(FileEntry {
name: name.to_string(),
size,
})
},
)(input)
}
fn parse_folder(input: &str) -> Result {
map(
delimited(tag("dir "), not_line_ending, newline),
|s: &str| ListEntry::Folder(s.to_string()),
)(input)
}
fn parse_cmd(input: &str) -> Result {
context(
"parse_cmd",
preceded(tuple((tag("$"), space1)), alt((parse_cd, parse_ls))),
)(input)
}
fn main() {
let args = Cli::parse();
let file = File::open(&args.file).unwrap();
let mut reader = BufReader::new(file);
let mut buf = String::new();
let mut cmds = Vec::new();
'block: loop {
let len = reader.read_line(&mut buf).unwrap();
if len == 0 {
buf.push(EOF_CHAR);
}
loop {
match parse_cmd(buf.as_str()) {
Ok((input, cmd)) => {
cmds.push(cmd);
if len == 0 && input.len() == 0 {
break 'block;
}
let _ = buf.drain(..(buf.len() - input.len()));
}
Err(err) => match err {
nom::Err::Incomplete(_) => continue 'block,
e @ _ => panic!("buf={} err={}", buf, e),
},
}
}
}
let mut loc = 0;
let mut arena = Arena(Vec::new());
arena.0.push(Node::new());
for cmd in cmds.into_iter() {
match cmd {
Commands::ChangeDir(dir) => match dir.as_str() {
"/" => loc = 0,
".." => loc = arena.get(loc).parent.unwrap(),
_ => loc = arena.get(loc).children[&dir],
},
Commands::List(lsv) => arena.consume(loc, lsv),
}
}
let rmdir_minsize = NEEDEDSPACE - (TOTALSPACE - arena.get(0).size);
let res: u64 = arena
.0
.into_iter()
.filter(|n| n.size >= rmdir_minsize)
.map(|n| n.size)
.min().unwrap();
println!("{res:?}");
}