Compare commits

...

2 Commits

Author SHA1 Message Date
Savanni D'Gerinel e3bf22c10f And there's part 2 2022-12-12 01:34:16 -05:00
Savanni D'Gerinel 0d22d43872 Solve 7a. Wow that took a lot of code. 2022-12-12 01:16:32 -05:00
4 changed files with 1636 additions and 1 deletions

1089
2022/data/day7.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use nom::{self, bytes::complete::tag, character::complete, sequence::separated_pair, IResult};
use nom::{self, bytes::complete::tag, character::complete, IResult};
use std::collections::HashSet;
const INPUT: &str = include_str!("../data/day4.txt");

543
2022/src/day7.rs Normal file
View File

@ -0,0 +1,543 @@
use nom::{
bytes::complete::tag,
character::{
complete::{line_ending, not_line_ending, u32},
streaming::space1,
},
combinator::eof,
multi::many0,
sequence::terminated,
IResult, Parser,
};
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
};
const INPUT: &str = include_str!("../data/day7.txt");
pub fn part1() -> String {
format!("{}", tree_sizes_100k(&input(INPUT)))
}
pub fn part2() -> String {
format!("{}", find_minimum_directory(&input(INPUT)).1)
}
#[derive(Clone, Debug, PartialEq)]
enum SessionLine {
Output(Node),
Command(Command),
}
#[derive(Clone, Debug, PartialEq)]
enum Node {
Dir(Dir),
File(File),
}
#[derive(Clone, Debug, PartialEq)]
struct Dir {
name: String,
children: Vec<PathBuf>,
}
#[derive(Clone, Debug, PartialEq)]
struct File {
name: String,
size: usize,
}
impl Default for Node {
fn default() -> Self {
Self::Dir(Dir {
name: "/".to_owned(),
children: vec![],
})
}
}
fn tree_sizes_100k(filesystem: &Filesystem) -> usize {
filesystem
.find_dirs()
.iter()
.map(|path| filesystem.size(path))
.filter(|size| *size <= 100000)
.fold(0, |cur, size| cur + size)
}
fn find_minimum_directory(filesystem: &Filesystem) -> (PathBuf, usize) {
let needed_space = 30000000 - filesystem.free_space();
let mut dir_sizes = filesystem
.find_dirs()
.iter()
.map(|path| (path.clone(), filesystem.size(path)))
.filter(|(_, size)| needed_space < (*size).try_into().unwrap())
.collect::<Vec<(PathBuf, usize)>>();
dir_sizes.sort_by(|(_, lsize), (_, rsize)| lsize.cmp(rsize));
dir_sizes[0].clone()
}
#[derive(Clone, Debug, PartialEq)]
enum Command {
CD(String),
LS,
}
#[derive(Clone, Debug, PartialEq)]
struct Filesystem(HashMap<PathBuf, Node>);
impl Filesystem {
fn insert(&mut self, path: PathBuf, node: Node) {
self.0.insert(path, node);
}
fn paths(&self) -> HashSet<PathBuf> {
self.0.keys().cloned().collect()
}
fn get(&self, path: &PathBuf) -> Option<&Node> {
self.0.get(path)
}
fn get_mut(&mut self, path: &PathBuf) -> Option<&mut Node> {
self.0.get_mut(path)
}
fn size(&self, path: &PathBuf) -> usize {
let node = self.0.get(path).unwrap();
match node {
Node::File(file) => file.size,
Node::Dir(dir) => dir
.children
.iter()
.fold(0, |sum, node| sum + self.size(node)),
}
}
fn find_dirs(&self) -> HashSet<PathBuf> {
self.paths()
.iter()
.filter(|path| match *self.get(path).unwrap() {
Node::Dir(_) => true,
_ => false,
})
.cloned()
.collect()
}
fn free_space(&self) -> usize {
70000000 - self.size(&PathBuf::from("/"))
}
}
impl Default for Filesystem {
fn default() -> Self {
Self(HashMap::new())
}
}
#[derive(Clone, Debug, PartialEq)]
struct State {
path: PathBuf,
filesystem: Filesystem,
}
impl Default for State {
fn default() -> Self {
let mut filesystem = Filesystem::default();
filesystem.insert(
PathBuf::from("/"),
Node::Dir(Dir {
name: "/".to_owned(),
children: vec![],
}),
);
Self {
path: PathBuf::from("/"),
filesystem,
}
}
}
impl State {
fn interpret_line(&mut self, line: SessionLine) {
match line {
SessionLine::Output(entry) => self.add_entry(entry),
SessionLine::Command(Command::CD(dest)) => self.change_dir(&dest),
SessionLine::Command(Command::LS) => (),
}
}
fn change_dir(&mut self, dir: &str) {
match dir {
".." => {
self.path.pop();
}
"/" => {
self.path = PathBuf::from("/");
}
val => {
self.path.push(val);
}
};
}
fn add_entry(&mut self, entry: Node) {
let mut path = self.path.clone();
match entry {
Node::Dir(ref dir) => path.push(dir.name.clone()),
Node::File(ref file) => path.push(file.name.clone()),
};
let parent = self.filesystem.get_mut(&self.path).unwrap();
match parent {
Node::Dir(dir) => dir.children.push(path.clone()),
Node::File(_) => panic!("adding a child to a file"),
}
self.filesystem.insert(path, entry);
}
}
fn input(data: &str) -> Filesystem {
let (_, session) = parse_session(data).unwrap();
let mut state = State::default();
session
.into_iter()
.for_each(|line| state.interpret_line(line));
state.filesystem
}
fn parse_session(input: &str) -> IResult<&str, Vec<SessionLine>> {
many0(
parse_command
.map(SessionLine::Command)
.or(parse_output.map(SessionLine::Output)),
)(input)
}
fn parse_command(input: &str) -> IResult<&str, Command> {
let (input, _) = tag("$ ")(input)?;
parse_cd
.map(|path| Command::CD(path))
.or(parse_ls.map(|_| Command::LS))
.parse(input)
}
fn parse_cd(input: &str) -> IResult<&str, String> {
let (input, _) = tag("cd ")(input)?;
let (input, path) = terminated(not_line_ending, line_ending.or(eof))(input)?;
Ok((input, path.to_owned()))
}
fn parse_ls(input: &str) -> IResult<&str, ()> {
let (input, _) = terminated(tag("ls"), line_ending.or(eof))(input)?;
Ok((input, ()))
}
fn parse_output(input: &str) -> IResult<&str, Node> {
parse_file_line
.map(|file| Node::File(file))
.or(parse_dir_line.map(|dir| Node::Dir(dir)))
.parse(input)
}
fn parse_file_line(input: &str) -> IResult<&str, File> {
let (input, size) = u32(input)?;
let (input, _) = space1(input)?;
let (input, name) = terminated(not_line_ending, line_ending.or(eof))(input)?;
Ok((
input,
File {
name: name.to_owned(),
size: size as usize,
},
))
}
fn parse_dir_line(input: &str) -> IResult<&str, Dir> {
let (input, _) = tag("dir ")(input)?;
let (input, name) = terminated(not_line_ending, line_ending.or(eof))(input)?;
Ok((
input,
Dir {
name: name.to_owned(),
children: vec![],
},
))
}
#[cfg(test)]
mod test {
use super::*;
const TEST_DATA: &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";
fn filesystem() -> Filesystem {
let mut filesystem = Filesystem::default();
filesystem.insert(
PathBuf::from("/"),
Node::Dir(Dir {
name: "/".to_owned(),
children: vec![
PathBuf::from("a"),
PathBuf::from("b.txt"),
PathBuf::from("c.dat"),
PathBuf::from("d"),
],
}),
);
filesystem.insert(
PathBuf::from("/a"),
Node::Dir(Dir {
name: "a".to_owned(),
children: vec![
PathBuf::from("e"),
PathBuf::from("f"),
PathBuf::from("g"),
PathBuf::from("h.lst"),
],
}),
);
filesystem.insert(
PathBuf::from("/a/e"),
Node::Dir(Dir {
name: "e".to_owned(),
children: vec![PathBuf::from("i")],
}),
);
filesystem.insert(
PathBuf::from("/a/e/i"),
Node::File(File {
name: "i".to_owned(),
size: 584,
}),
);
filesystem.insert(
PathBuf::from("/a/f"),
Node::File(File {
name: "f".to_owned(),
size: 29116,
}),
);
filesystem.insert(
PathBuf::from("/a/g"),
Node::File(File {
name: "g".to_owned(),
size: 2557,
}),
);
filesystem.insert(
PathBuf::from("/a/h.lst"),
Node::File(File {
name: "h.lst".to_owned(),
size: 62596,
}),
);
filesystem.insert(
PathBuf::from("/b.txt"),
Node::File(File {
name: "b.txt".to_owned(),
size: 14848514,
}),
);
filesystem.insert(
PathBuf::from("/c.dat"),
Node::File(File {
name: "c.dat".to_owned(),
size: 8504156,
}),
);
filesystem.insert(
PathBuf::from("/d"),
Node::Dir(Dir {
name: "d".to_owned(),
children: vec![
PathBuf::from("j"),
PathBuf::from("d.log"),
PathBuf::from("d.ext"),
PathBuf::from("k"),
],
}),
);
filesystem.insert(
PathBuf::from("/d/j"),
Node::File(File {
name: "j".to_owned(),
size: 4060174,
}),
);
filesystem.insert(
PathBuf::from("/d/d.log"),
Node::File(File {
name: "d.log".to_owned(),
size: 8033020,
}),
);
filesystem.insert(
PathBuf::from("/d/d.ext"),
Node::File(File {
name: "d.ext".to_owned(),
size: 5626152,
}),
);
filesystem.insert(
PathBuf::from("/d/k"),
Node::File(File {
name: "k".to_owned(),
size: 7214296,
}),
);
filesystem
}
fn with_input<F>(test: F)
where
F: Fn(Filesystem) -> () + std::panic::UnwindSafe,
{
test(input(TEST_DATA));
}
#[test]
fn it_parses_command_lines() {
let (_, res) = parse_command("$ cd /").unwrap();
assert_eq!(Command::CD("/".to_owned()), res);
let (_, res) = parse_command("$ ls").unwrap();
assert_eq!(Command::LS, res);
}
#[test]
fn it_parses_output_lines() {
let (_, res) = parse_output("8033020 d.log").unwrap();
assert_eq!(
Node::File(File {
name: "d.log".to_owned(),
size: 8033020
}),
res
);
let (_, res) = parse_output("dir e").unwrap();
assert_eq!(
Node::Dir(Dir {
name: "e".to_owned(),
children: vec![],
}),
res
);
}
#[test]
fn it_parses_a_session() {
let (_, res) = parse_session(TEST_DATA).unwrap();
assert_eq!(res[0], SessionLine::Command(Command::CD("/".to_owned())));
assert_eq!(res[1], SessionLine::Command(Command::LS));
assert_eq!(
res[2],
SessionLine::Output(Node::Dir(Dir {
name: "a".to_owned(),
children: vec![]
}))
);
assert_eq!(
res[3],
SessionLine::Output(Node::File(File {
name: "b.txt".to_owned(),
size: 14848514
}))
);
assert_eq!(res[6], SessionLine::Command(Command::CD("a".to_owned())));
}
#[test]
fn it_parses_the_tree() {
with_input(|fs| {
assert_eq!(fs.paths(), filesystem().paths());
assert_eq!(
fs.get(&PathBuf::from("/c.dat")).map(|c| c.clone()),
Some(Node::File(File {
name: "c.dat".to_owned(),
size: 8504156
}))
);
assert_eq!(
fs.get(&PathBuf::from("/d")).map(|c| c.clone()),
Some(Node::Dir(Dir {
name: "d".to_owned(),
children: vec![
PathBuf::from("/d/j"),
PathBuf::from("/d/d.log"),
PathBuf::from("/d/d.ext"),
PathBuf::from("/d/k"),
]
}))
);
});
}
#[test]
fn it_scans_a_file_size() {
with_input(|fs| {
assert_eq!(fs.size(&PathBuf::from("/b.txt")), 14848514);
assert_eq!(fs.size(&PathBuf::from("/a/e")), 584);
assert_eq!(fs.size(&PathBuf::from("/a")), 94853);
assert_eq!(fs.size(&PathBuf::from("/d")), 24933642);
assert_eq!(fs.size(&PathBuf::from("/")), 48381165);
});
}
#[test]
fn it_finds_all_dirs_under_100000() {
with_input(|fs| {
assert_eq!(tree_sizes_100k(&fs), 95437);
})
}
#[test]
fn it_solves_part1() {
assert_eq!(tree_sizes_100k(&input(INPUT)), 1792222);
}
#[test]
fn it_measures_free_space() {
with_input(|fs| assert_eq!(fs.free_space(), 21618835));
}
#[test]
fn it_finds_smallest_needed_dir() {
with_input(|fs| assert_eq!(find_minimum_directory(&fs), (PathBuf::from("/d"), 24933642)));
}
#[test]
fn it_solves_part2() {
assert_eq!(find_minimum_directory(&input(INPUT)).1, 1112963);
}
}

View File

@ -4,6 +4,7 @@ mod day3;
mod day4;
mod day5;
mod day6;
mod day7;
fn main() {
let day = std::env::args().skip(1).next();
@ -21,6 +22,8 @@ fn main() {
Some("5b") => day5::part2(),
Some("6a") => day6::part1(),
Some("6b") => day6::part2(),
Some("7a") => day7::part1(),
Some("7b") => day7::part2(),
_ => panic!("unrecognized day"),
};