Compare commits
No commits in common. "e3bf22c10f4c61031f080b0c703598cf66143c1e" and "84de761c1af77ca804aaaeba13caa617ae1e4376" have entirely different histories.
e3bf22c10f
...
84de761c1a
1089
2022/data/day7.txt
1089
2022/data/day7.txt
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
use nom::{self, bytes::complete::tag, character::complete, IResult};
|
||||
use nom::{self, bytes::complete::tag, character::complete, sequence::separated_pair, IResult};
|
||||
use std::collections::HashSet;
|
||||
|
||||
const INPUT: &str = include_str!("../data/day4.txt");
|
||||
|
|
543
2022/src/day7.rs
543
2022/src/day7.rs
|
@ -1,543 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ mod day3;
|
|||
mod day4;
|
||||
mod day5;
|
||||
mod day6;
|
||||
mod day7;
|
||||
|
||||
fn main() {
|
||||
let day = std::env::args().skip(1).next();
|
||||
|
@ -22,8 +21,6 @@ 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"),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue