day(07): add initial solution
Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
parent
33185bdae3
commit
b3b26cd6a9
2 changed files with 265 additions and 0 deletions
23
samples/day07.txt
Normal file
23
samples/day07.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
$ 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
|
242
src/bin/day07.rs
Normal file
242
src/bin/day07.rs
Normal file
|
@ -0,0 +1,242 @@
|
|||
use std::{cell::RefCell, cmp::min, collections::BTreeMap, rc::Rc, str::FromStr};
|
||||
|
||||
use aoc_2022::*;
|
||||
|
||||
use color_eyre::{eyre::Result, Report};
|
||||
use itertools::Itertools;
|
||||
use tracing::*;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
type FileHandle = Rc<RefCell<AocFile>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AocFile {
|
||||
File(usize),
|
||||
Directory(BTreeMap<String, FileHandle>),
|
||||
}
|
||||
|
||||
impl Default for AocFile {
|
||||
fn default() -> Self {
|
||||
AocFile::Directory(BTreeMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl AocFile {
|
||||
fn dir() -> FileHandle {
|
||||
Rc::new(RefCell::new(AocFile::Directory(BTreeMap::new())))
|
||||
}
|
||||
|
||||
fn file(size: usize) -> FileHandle {
|
||||
Rc::new(RefCell::new(AocFile::File(size)))
|
||||
}
|
||||
|
||||
fn add_file(&mut self, name: &str, size: usize) {
|
||||
if let AocFile::Directory(files) = self {
|
||||
files.insert(name.to_string(), AocFile::file(size));
|
||||
return;
|
||||
}
|
||||
|
||||
panic!("cannot cd in file")
|
||||
}
|
||||
|
||||
fn cd(&mut self, dir: &str) -> FileHandle {
|
||||
if let AocFile::Directory(files) = self {
|
||||
let handle = files.entry(dir.to_string()).or_default().clone();
|
||||
|
||||
// debug!("Entering directory: {:?}", handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
panic!("cannot cd in file")
|
||||
}
|
||||
|
||||
fn size_under_100000(&self) -> (bool, usize, usize) {
|
||||
match self {
|
||||
AocFile::File(s) => (false, 0, *s),
|
||||
AocFile::Directory(files) => {
|
||||
let (running_total, size) = files
|
||||
.values()
|
||||
.map(|f| f.borrow().size_under_100000())
|
||||
.fold((0_usize, 0_usize), |(mut running, size), (dir, r, s)| {
|
||||
// debug!("Checking ({}, {}, {})", dir, r, s);
|
||||
|
||||
if dir && s <= 100000 {
|
||||
// debug!("Is small (hehe) directory, adding to total: {s}");
|
||||
running += s;
|
||||
}
|
||||
|
||||
(r + running, size + s)
|
||||
});
|
||||
|
||||
(true, running_total, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn smallest_bigger(&self, required: usize) -> (bool, usize, usize) {
|
||||
match self {
|
||||
AocFile::File(s) => (false, usize::MAX, *s),
|
||||
AocFile::Directory(files) => {
|
||||
let (mut dir_min, size) = files
|
||||
.values()
|
||||
.map(|f| f.borrow().smallest_bigger(required))
|
||||
.fold(
|
||||
(usize::MAX, 0_usize),
|
||||
|(mut current_min, size), (dir, r_min, s)| {
|
||||
if dir && s >= required {
|
||||
// we got back from a subdirectory
|
||||
current_min = min(current_min, s);
|
||||
}
|
||||
|
||||
// also check local solutions in subdirectories
|
||||
(min(current_min, r_min), size + s)
|
||||
},
|
||||
);
|
||||
|
||||
if size >= required {
|
||||
dir_min = min(dir_min, size);
|
||||
}
|
||||
|
||||
(true, dir_min, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Filesystem {
|
||||
root: FileHandle,
|
||||
cwd: Vec<FileHandle>,
|
||||
}
|
||||
|
||||
impl Filesystem {
|
||||
fn new() -> Filesystem {
|
||||
let root = AocFile::dir();
|
||||
|
||||
Filesystem {
|
||||
root: root.clone(),
|
||||
cwd: vec![root],
|
||||
}
|
||||
}
|
||||
|
||||
fn cd(&mut self, dir: &str) {
|
||||
// debug!("swish swish: {}", dir);
|
||||
|
||||
match dir {
|
||||
".." => {
|
||||
self.cwd.pop();
|
||||
}
|
||||
"/" => {
|
||||
self.cwd.clear();
|
||||
self.cwd.push(self.root.clone());
|
||||
}
|
||||
_ => {
|
||||
let idx = self.cwd.len() - 1;
|
||||
let subdir = self.cwd[idx].borrow_mut().cd(dir);
|
||||
self.cwd.push(subdir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn touch(&mut self, name: &str, size: usize) {
|
||||
let idx = self.cwd.len() - 1;
|
||||
// debug!("touchy touchy: {} ({})", name, size);
|
||||
self.cwd[idx].borrow_mut().add_file(name, size);
|
||||
}
|
||||
|
||||
fn run_command(&mut self, command: &str) {
|
||||
if command.starts_with("cd ") {
|
||||
let parts = command.split_ascii_whitespace().collect_vec();
|
||||
self.cd(parts[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
for file in command.lines().skip(1) {
|
||||
let parts = file.split_ascii_whitespace().collect_vec();
|
||||
if parts[0] == "dir" {
|
||||
/* no-op */
|
||||
} else {
|
||||
let name = parts[1];
|
||||
let size: usize = parts[0].parse().unwrap();
|
||||
self.touch(name, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Filesystem {
|
||||
type Err = Report;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut fs = Filesystem::new();
|
||||
|
||||
for command in s.trim_start_matches("$ ").split("\n$ ") {
|
||||
// debug!("Running command: {}", command);
|
||||
fs.run_command(command);
|
||||
}
|
||||
|
||||
Ok(fs)
|
||||
}
|
||||
}
|
||||
|
||||
type Input = Filesystem;
|
||||
type Output = usize;
|
||||
|
||||
fn part_1(input: &Input) -> Output {
|
||||
input.root.borrow().size_under_100000().1
|
||||
}
|
||||
|
||||
fn part_2(input: &Input) -> Output {
|
||||
let (total, needed) = (70000000, 30000000);
|
||||
let used = input.root.borrow().size_under_100000().2;
|
||||
// to_be_freed >= needed - (total - used)
|
||||
let to_be_freed = needed - (total - used);
|
||||
|
||||
// debug!(
|
||||
// "Used: {}, free: {}, to_be_freed: {}",
|
||||
// used,
|
||||
// total - used,
|
||||
// to_be_freed
|
||||
// );
|
||||
|
||||
input.root.borrow().smallest_bigger(to_be_freed).1
|
||||
}
|
||||
|
||||
fn parse_input(pathname: &str) -> Input {
|
||||
file_to_string(pathname).parse().unwrap()
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.with_target(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.without_time()
|
||||
.compact()
|
||||
.init();
|
||||
color_eyre::install()?;
|
||||
|
||||
let input = parse_input("inputs/day07.txt");
|
||||
|
||||
info!("Part 1: {}", part_1(&input));
|
||||
info!("Part 2: {}", part_2(&input));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod day_06 {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() {
|
||||
let sample = parse_input("samples/day07.txt");
|
||||
assert_eq!(part_1(&sample), 95437);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() {
|
||||
let sample = parse_input("samples/day07.txt");
|
||||
assert_eq!(part_2(&sample), 24933642);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue