Files
aoc2022/src/day07.rs

167 lines
4.2 KiB
Rust

use std::collections::BTreeMap;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_till1};
use nom::character::complete::{newline, space1};
use nom::character::{complete, is_newline};
use nom::multi::separated_list1;
use nom::IResult;
pub fn process_part_1(input: &str) -> u32 {
let (_, commands) = parse_commands(input).unwrap();
let directories = build_directory_tree_from_commands(commands);
directories.values().filter(|&size| *size <= 100_000).sum()
}
pub fn process_part_2(input: &str) -> u32 {
let (_, commands) = parse_commands(input).unwrap();
let directories = build_directory_tree_from_commands(commands);
let total_space = 70_000_000;
let required_space = 30_000_000;
let current_space = total_space - *directories.get("").unwrap();
let wanted_space = required_space - current_space;
directories
.values()
.filter(|&size| *size >= wanted_space)
.map(|&size| size)
.min()
.unwrap_or(0)
}
fn build_directory_tree_from_commands(commands: Vec<Command>) -> BTreeMap<String, u32> {
commands
.iter()
.fold((vec![], BTreeMap::new()), |(mut cwd, mut dirs), command| {
match command {
Command::Cd("/") => {
cwd = vec![];
}
Command::Cd("..") => {
cwd.pop();
}
Command::Cd(dir) => {
cwd.push(*dir);
}
Command::Ls(files) => {
// We don't actually care about individual files, just sum the files together
// so we aren't looking up the BTreeMap as often
let total_size = files
.iter()
.filter_map(|e| match e {
DirEntry::File { size, .. } => Some(size),
_ => None,
})
.sum();
for i in 0..=cwd.len() {
dirs.entry(cwd[..i].join("/"))
.and_modify(|f| *f += total_size)
.or_insert(total_size);
}
}
}
(cwd, dirs)
})
.1
}
#[derive(Debug)]
enum Command<'a> {
Cd(&'a str),
Ls(Vec<DirEntry<'a>>),
}
#[derive(Debug)]
enum DirEntry<'a> {
Dir(&'a str),
#[allow(dead_code)]
File {
name: &'a str,
size: u32,
},
}
fn parse_commands(input: &str) -> IResult<&str, Vec<Command>> {
separated_list1(newline, parse_command)(input)
}
fn parse_command(input: &str) -> IResult<&str, Command> {
let (input, _) = tag("$ ")(input)?;
alt((parse_cd, parse_ls))(input)
}
fn parse_cd(input: &str) -> IResult<&str, Command> {
let (input, _) = tag("cd")(input)?;
let (input, _) = space1(input)?;
let (input, arg) = parse_file_name(input)?;
Ok((input, Command::Cd(arg)))
}
fn parse_ls(input: &str) -> IResult<&str, Command> {
let (input, _) = tag("ls")(input)?;
let (input, _) = newline(input)?;
let (input, entries) =
separated_list1(tag("\n"), alt((parse_dir_entry_dir, parse_dir_entry_file)))(input)?;
Ok((input, Command::Ls(entries)))
}
fn parse_dir_entry_dir(input: &str) -> IResult<&str, DirEntry> {
let (input, _) = tag("dir ")(input)?;
let (input, name) = parse_file_name(input)?;
Ok((input, DirEntry::Dir(name)))
}
fn parse_dir_entry_file(input: &str) -> IResult<&str, DirEntry> {
let (input, size) = complete::u32(input)?;
let (input, _) = tag(" ")(input)?;
let (input, name) = parse_file_name(input)?;
Ok((input, DirEntry::File { name, size }))
}
fn parse_file_name(input: &str) -> IResult<&str, &str> {
take_till1(|c| is_newline(c as u8))(input)
}
#[cfg(test)]
mod tests {
use super::*;
const INPUT: &str = "$ cd /
$ ls
dir a
14848514 b.txt
8504156 c.dat
dir d
$ cd a
$ ls
dir e
29116 f
2557 g
62596 h.lst
$ cd e
$ ls
584 i
$ cd ..
$ cd ..
$ cd d
$ ls
4060174 j
8033020 d.log
5626152 d.ext
7214296 k";
#[test]
fn day1() {
assert_eq!(process_part_1(INPUT), 95437);
}
#[test]
fn day2() {
assert_eq!(process_part_2(INPUT), 24933642);
}
}