first commit
This commit is contained in:
commit
cfe3798d17
42 changed files with 9617 additions and 0 deletions
382
2022/day7/src/main.rs
Executable file
382
2022/day7/src/main.rs
Executable file
|
@ -0,0 +1,382 @@
|
|||
#![allow(unused_imports)]
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
env::current_dir,
|
||||
fmt::{format, Display},
|
||||
fs,
|
||||
iter::empty,
|
||||
ops::DerefMut,
|
||||
};
|
||||
use transiter::{IntoTransIter, TransIter};
|
||||
|
||||
use dbg_pls::{color, DebugPls};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_till1, take_until1, take_while1},
|
||||
character::{
|
||||
complete::{digit1, newline, not_line_ending},
|
||||
is_alphabetic,
|
||||
},
|
||||
combinator::{map_res, opt},
|
||||
multi::many0,
|
||||
AsChar, IResult,
|
||||
};
|
||||
|
||||
mod utils;
|
||||
|
||||
// PUZZLE: https://web.archive.org/web/20230223113025/https://adventofcode.com/2022/day/7
|
||||
|
||||
#[derive(Debug, DebugPls)]
|
||||
enum File {
|
||||
Plain { name: String, size: usize },
|
||||
Dir { name: String, children: Vec<File> },
|
||||
}
|
||||
#[derive(Debug, DebugPls)]
|
||||
#[allow(non_camel_case_types)]
|
||||
enum Command {
|
||||
cd { target: String },
|
||||
ls { files: Vec<File> },
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
println!("Hello, AdventOfCode - day 7!");
|
||||
let content = fs::read_to_string("./data").expect("Read ./input");
|
||||
|
||||
println!("PART 1 - RESULT: {}", process_part1(content.clone()));
|
||||
println!();
|
||||
println!("PART 2 - RESULT: {}", process_part2(content));
|
||||
}
|
||||
|
||||
fn process_part1(content: String) -> String {
|
||||
let root = parse_session(content.as_str());
|
||||
|
||||
let dirs = root.dirs_recursive();
|
||||
println!("Dirs: {}", color(&dirs.iter().map(|f| f.name()).collect::<Vec<String>>()));
|
||||
let dirs_under_size_limit = dirs
|
||||
.iter()
|
||||
.filter(|dir| dir.total_size() <= 100000)
|
||||
.collect::<Vec<_>>();
|
||||
println!("Dirs under size limit: {}", color(&dirs_under_size_limit));
|
||||
|
||||
let total_size = dirs_under_size_limit.iter().map(|f| f.total_size()).sum::<usize>();
|
||||
total_size.to_string()
|
||||
}
|
||||
|
||||
fn process_part2(content: String) -> String {
|
||||
let root = parse_session(content.as_str());
|
||||
let used = root.total_size();
|
||||
let capacity = 70000000;
|
||||
let available = capacity - used;
|
||||
println!("Total size: {} ({} available)", used, available);
|
||||
let required = 30000000;
|
||||
let to_be_freed = required - available;
|
||||
println!("Must be freed: {}", to_be_freed);
|
||||
|
||||
let dirs = root.dirs_recursive();
|
||||
let mut big_enough = dirs
|
||||
.iter()
|
||||
.filter(|dir| dir.total_size() >= to_be_freed)
|
||||
.collect::<Vec<_>>();
|
||||
big_enough.sort_unstable_by_key(|f| f.total_size());
|
||||
println!(
|
||||
"Big enough: {}",
|
||||
color(
|
||||
&big_enough
|
||||
.iter()
|
||||
.map(|f| format!("[{}] {}", f.total_size(), f.name()))
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
);
|
||||
|
||||
let smallest_sufficient = big_enough.first().expect("a big enough dir");
|
||||
|
||||
smallest_sufficient.total_size().to_string()
|
||||
}
|
||||
|
||||
fn parse_session(content: &str) -> File {
|
||||
let mut root = File::Dir {
|
||||
name: "/".into(),
|
||||
children: vec![],
|
||||
};
|
||||
let mut input = content;
|
||||
let mut cwd: Vec<String> = vec![];
|
||||
// let mut current_dir = root;
|
||||
while input.len() > 0 {
|
||||
let (remaining_input, cmd) = parse_command(input).expect("parse command");
|
||||
input = remaining_input;
|
||||
color!(&cmd);
|
||||
let current_dir: &mut File = get_dir(&mut root, &cwd);
|
||||
if let File::Dir {
|
||||
children,
|
||||
name: current_dir_name,
|
||||
} = current_dir
|
||||
{
|
||||
match cmd {
|
||||
Command::cd { target } => {
|
||||
match target.as_str() {
|
||||
"/" => {
|
||||
cwd = vec![];
|
||||
}
|
||||
".." => {
|
||||
cwd.pop();
|
||||
}
|
||||
_ => {
|
||||
let target = children
|
||||
.iter()
|
||||
.find(|f| {
|
||||
if let File::Dir { name, .. } = f {
|
||||
name == &target
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.expect("find cd target");
|
||||
|
||||
// current_dir = target;
|
||||
cwd.push(target.name())
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"Changed into {} => cwd: {}",
|
||||
target,
|
||||
cwd /* .iter().map(|f| f.get_name()).collect::<Vec<_>>() */
|
||||
.join("/")
|
||||
)
|
||||
}
|
||||
Command::ls { mut files } => {
|
||||
println!("Adding children to {}: {:#?}", current_dir_name, color(&cwd));
|
||||
children.append(&mut files);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("cwd {} is not a dir: {:?}", cwd.join("/"), current_dir)
|
||||
}
|
||||
}
|
||||
println!();
|
||||
println!("Final Directory structure: {}", color(&root));
|
||||
root
|
||||
}
|
||||
fn get_dir<'a>(root: &'a mut File, path: &'a Vec<String>) -> &'a mut File {
|
||||
let mut cwd = root;
|
||||
for target in path {
|
||||
if let File::Dir {
|
||||
ref mut children, ..
|
||||
} = cwd
|
||||
{
|
||||
cwd = children
|
||||
.into_iter()
|
||||
.find(|f| {
|
||||
if let File::Dir { name, .. } = f {
|
||||
name == target
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.expect("find cd target")
|
||||
.borrow_mut()
|
||||
} else {
|
||||
panic!("get_dir path '{}' is not a dir: {:?}", path.join("/"), cwd)
|
||||
}
|
||||
}
|
||||
cwd
|
||||
}
|
||||
|
||||
fn parse_command(input: &str) -> IResult<&str, Command> {
|
||||
let (input, cmd_name) = parse_command_name(input).expect("parse command name");
|
||||
Ok(match cmd_name {
|
||||
"ls" => {
|
||||
let (input, files) = parse_ls_output(input).expect("parse command output");
|
||||
(input, Command::ls { files })
|
||||
}
|
||||
"cd" => {
|
||||
let (input, target) = parse_command_arg(input).expect("parse command arg");
|
||||
let (input, _) = newline(input)?;
|
||||
(
|
||||
input,
|
||||
Command::cd {
|
||||
target: target.into(),
|
||||
},
|
||||
)
|
||||
}
|
||||
&_ => panic!("invalid command {}", cmd_name),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_ls_output(input: &str) -> IResult<&str, Vec<File>> {
|
||||
let (input, output) = parse_command_output(input)?;
|
||||
let (remaining_output, files) = many0(parse_file)(output)?;
|
||||
if remaining_output.len() != 0 {
|
||||
panic!("ls output not fully consumed: '{}'", remaining_output);
|
||||
}
|
||||
|
||||
Ok((input, files))
|
||||
}
|
||||
|
||||
fn parse_file(input: &str) -> IResult<&str, File> {
|
||||
let (input, dir_or_size) = alt((tag("dir"), digit1))(input)?;
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, name) = till_eol(input)?;
|
||||
let (input, _) = opt(newline)(input)?;
|
||||
|
||||
let file = match dir_or_size {
|
||||
"dir" => File::Dir {
|
||||
name: name.into(),
|
||||
children: vec![],
|
||||
},
|
||||
size => File::Plain {
|
||||
name: name.into(),
|
||||
size: size.parse().expect("parsable size"),
|
||||
},
|
||||
};
|
||||
color!(&file, input);
|
||||
Ok((input, file))
|
||||
}
|
||||
|
||||
fn parse_command_name(input: &str) -> IResult<&str, &str> {
|
||||
let (input, _) = tag("$")(input)?;
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, cmd_name) = take_while1(AsChar::is_alpha)(input)?;
|
||||
|
||||
Ok((input, cmd_name))
|
||||
}
|
||||
|
||||
fn parse_command_arg(input: &str) -> IResult<&str, &str> {
|
||||
let (input, _) = tag(" ")(input)?;
|
||||
let (input, arg) = till_eol(input)?;
|
||||
Ok((input, arg))
|
||||
}
|
||||
|
||||
fn parse_command_output(input: &str) -> IResult<&str, &str> {
|
||||
let (input, _) = newline(input)?;
|
||||
let (input, output) = take_while1(|c: char| c != '$')(input)?;
|
||||
Ok((input, output))
|
||||
}
|
||||
|
||||
fn till_eol(input: &str) -> IResult<&str, &str> {
|
||||
Ok(take_till1(|c: char| c.is_whitespace())(input)?)
|
||||
}
|
||||
|
||||
impl File {
|
||||
fn name(&self) -> String {
|
||||
match self {
|
||||
File::Dir { name, .. } => name.clone(),
|
||||
File::Plain { name, .. } => name.clone(),
|
||||
}
|
||||
}
|
||||
fn total_size(&self) -> usize {
|
||||
match self {
|
||||
File::Plain { size, .. } => *size,
|
||||
File::Dir { children, .. } => children.into_iter().map(|f| f.total_size()).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dirs_recursive(&self) -> Vec<&File> {
|
||||
self.trans_iter_with(|file| {
|
||||
if let File::Dir { children, .. } = file {
|
||||
children
|
||||
.into_iter()
|
||||
.filter(|f| matches!(f, File::Dir { .. }))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// impl Display for File {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// match self {
|
||||
// Self::cd { target } => {
|
||||
// write!(f, "cd {}", target)?;
|
||||
// }
|
||||
// Self::ls { files } => {
|
||||
// write!(
|
||||
// f,
|
||||
// "ls\n{:?}",
|
||||
// files
|
||||
// .iter()
|
||||
// .map(|f| format!("{}", color(f)))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join("\n")
|
||||
// )?;
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
// impl Display for Command {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// match self {
|
||||
// Self::cd { target } => {
|
||||
// write!(f, "cd {}", target)?;
|
||||
// }
|
||||
// Self::ls { files } => {
|
||||
// write!(
|
||||
// f,
|
||||
// "ls\n{:?}",
|
||||
// files
|
||||
// .iter()
|
||||
// .map(|f| format!("{}", color(f)))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join("\n")
|
||||
// )?;
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EXAMPLE: &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 example_part1() {
|
||||
let result = process_part1(EXAMPLE.into());
|
||||
assert_eq!(result, "95437");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_part1() {
|
||||
let result = process_part1(fs::read_to_string("./input").expect("Read ./input"));
|
||||
assert_eq!(result, "1443806");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_part2() {
|
||||
let result = process_part2(EXAMPLE.into());
|
||||
assert_eq!(result, "24933642");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_part2() {
|
||||
let result = process_part2(fs::read_to_string("./input").expect("Read ./input"));
|
||||
assert_eq!(result, "942298");
|
||||
}
|
||||
}
|
12
2022/day7/src/utils.rs
Executable file
12
2022/day7/src/utils.rs
Executable file
|
@ -0,0 +1,12 @@
|
|||
/// https://stackoverflow.com/a/69324393/1633985
|
||||
#[macro_export]
|
||||
macro_rules! cast {
|
||||
($target: expr, $pat: path) => {{
|
||||
if let $pat(a) = $target {
|
||||
// #1
|
||||
a
|
||||
} else {
|
||||
panic!("mismatch variant when cast to {}", stringify!($pat)); // #2
|
||||
}
|
||||
}};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue