Solve 7a. Wow that took a lot of code.
This commit is contained in:
parent
84de761c1a
commit
0d22d43872
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
const INPUT: &str = include_str!("../data/day4.txt");
|
const INPUT: &str = include_str!("../data/day4.txt");
|
||||||
|
|
|
@ -0,0 +1,516 @@
|
||||||
|
use nom::{
|
||||||
|
bytes::complete::tag,
|
||||||
|
character::{
|
||||||
|
complete::{line_ending, not_line_ending, u32},
|
||||||
|
streaming::space1,
|
||||||
|
},
|
||||||
|
combinator::eof,
|
||||||
|
multi::many0,
|
||||||
|
sequence::{self, 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 {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 paths(&self) -> HashSet<PathBuf> {
|
||||||
|
self.filesystem.paths()
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ mod day3;
|
||||||
mod day4;
|
mod day4;
|
||||||
mod day5;
|
mod day5;
|
||||||
mod day6;
|
mod day6;
|
||||||
|
mod day7;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let day = std::env::args().skip(1).next();
|
let day = std::env::args().skip(1).next();
|
||||||
|
@ -21,6 +22,8 @@ fn main() {
|
||||||
Some("5b") => day5::part2(),
|
Some("5b") => day5::part2(),
|
||||||
Some("6a") => day6::part1(),
|
Some("6a") => day6::part1(),
|
||||||
Some("6b") => day6::part2(),
|
Some("6b") => day6::part2(),
|
||||||
|
Some("7a") => day7::part1(),
|
||||||
|
Some("7b") => day7::part2(),
|
||||||
_ => panic!("unrecognized day"),
|
_ => panic!("unrecognized day"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue