summaryrefslogtreecommitdiffstats
path: root/aoc_2022/src/day_7.rs
diff options
context:
space:
mode:
Diffstat (limited to 'aoc_2022/src/day_7.rs')
-rw-r--r--aoc_2022/src/day_7.rs217
1 files changed, 217 insertions, 0 deletions
diff --git a/aoc_2022/src/day_7.rs b/aoc_2022/src/day_7.rs
new file mode 100644
index 0000000..09eb1ef
--- /dev/null
+++ b/aoc_2022/src/day_7.rs
@@ -0,0 +1,217 @@
+use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr};
+
+use anyhow::{anyhow, Context, Result};
+
+use aoc::{Problem, Solution};
+
+enum Cmd {
+ Root,
+ Parent,
+ Child(String),
+ LS(Vec<Entry>),
+}
+
+impl FromStr for Cmd {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let (cmd, arg) = s.trim().split_at(2);
+ let cmd_arg = (cmd.trim(), arg.trim());
+ match cmd_arg {
+ ("cd", "/") => Ok(Self::Root),
+ ("cd", "..") => Ok(Self::Parent),
+ ("cd", child) => Ok(Self::Child(child.to_owned())),
+ ("ls", arg) => {
+ let entries = arg
+ .trim()
+ .lines()
+ .map(Entry::from_str)
+ .collect::<Result<Vec<_>>>()?;
+ Ok(Cmd::LS(entries))
+ }
+ _ => Err(anyhow!("Failed to parse Command from str: {}", s)),
+ }
+ }
+}
+
+enum Entry {
+ Dir(String),
+ File((String, usize)),
+}
+
+impl FromStr for Entry {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let (entry_type, name) = s
+ .split_once(' ')
+ .context(format!("Failed to parse Command from str: {}", s))?;
+ match entry_type {
+ "dir" => Ok(Self::Dir(name.to_owned())),
+ t => Ok(Self::File((name.to_owned(), t.parse()?))),
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+struct FileSystem {
+ entries: HashMap<Vec<String>, usize>,
+ cwd: Vec<String>,
+}
+
+impl FileSystem {
+ fn add(&mut self, size: usize) {
+ self.entries
+ .entry(self.cwd.to_owned())
+ .and_modify(|n| *n += size)
+ .or_insert(size);
+ }
+
+ fn pop_and_add(&mut self) -> Option<String> {
+ let size = *self.entries.get(&self.cwd).unwrap_or(&0);
+ let value = self.cwd.pop();
+ self.add(size);
+ value
+ }
+
+ fn exec_cmd(&mut self, cmd: Cmd) {
+ match cmd {
+ Cmd::Root => self.cwd = vec![],
+ Cmd::Parent => {
+ self.pop_and_add();
+ }
+ Cmd::Child(d) => self.cwd.push(d),
+ Cmd::LS(entries) => {
+ for entry in entries.into_iter() {
+ match entry {
+ Entry::File((_, size)) => self.add(size),
+ Entry::Dir(_) => {}
+ }
+ }
+ }
+ }
+ }
+}
+
+impl Deref for FileSystem {
+ type Target = HashMap<Vec<String>, usize>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.entries
+ }
+}
+
+impl FromStr for FileSystem {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ s.split('$')
+ .filter_map(|l| match l.trim() {
+ _ if l.is_empty() => None,
+ l => Some(l),
+ })
+ .flat_map(Cmd::from_str)
+ .collect::<Vec<Cmd>>()
+ .try_into()
+ }
+}
+
+impl TryFrom<Vec<Cmd>> for FileSystem {
+ type Error = anyhow::Error;
+
+ fn try_from(value: Vec<Cmd>) -> Result<Self, Self::Error> {
+ let mut fs = Self::default();
+ for cmd in value {
+ fs.exec_cmd(cmd)
+ }
+
+ while !fs.cwd.is_empty() {
+ fs.exec_cmd(Cmd::Parent)
+ }
+
+ Ok(fs)
+ }
+}
+
+impl Display for FileSystem {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "path\tsize")?;
+ for (path, size) in &self.entries {
+ writeln!(f, "/{}\t{}", path.join("/"), size)?;
+ }
+ Ok(())
+ }
+}
+
+pub struct Day7;
+
+impl Problem for Day7 {
+ const DAY: u8 = 7;
+
+ const INPUT: &'static str = include_str!("../input/day_7.txt");
+}
+
+impl Solution for Day7 {
+ type Answer1 = usize;
+
+ type Answer2 = usize;
+
+ fn part_1(input: &str) -> Result<Self::Answer1> {
+ let fs: FileSystem = input.parse()?;
+ Ok(fs.values().filter(|&s| *s < 100_000).sum())
+ }
+
+ fn part_2(input: &str) -> Result<Self::Answer2> {
+ let fs: FileSystem = input.parse()?;
+ let used = *fs.get(&Vec::new()).context("Failed to get root size")?;
+
+ fs.values()
+ .filter_map(|&dir_size| match dir_size {
+ k if used - k < 40_000_000 => Some(k),
+ _ => None,
+ })
+ .min()
+ .context("No directory found")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const TEST_INPUT: &str = indoc::indoc! {r#"
+ $ 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 test_part_1_example() -> Result<()> {
+ Ok(assert_eq!(95437, Day7::part_1(TEST_INPUT)?))
+ }
+
+ #[test]
+ fn test_part_2_example() -> Result<()> {
+ Ok(assert_eq!(24933642, Day7::part_2(TEST_INPUT)?))
+ }
+}