use anyhow::Context; use anyhow::Result; use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::take_until; use nom::character::complete::newline; use nom::combinator::map; use nom::combinator::opt; use nom::multi::fold_many0; use nom::sequence::delimited; use nom::sequence::preceded; use nom::sequence::terminated; use nom::sequence::tuple; use nom::IResult; use crate::common::parse_input; fn parse_dir<'a>( input: &'a [u8], dirs: &mut Vec, dir_stack: &mut Vec<&'a [u8]>, ) -> IResult<&'a [u8], u32> { use nom::character::complete::u32; enum Entry<'a> { File(u32), Dir(&'a [u8]), } let initial_len = dir_stack.len(); let (mut input, mut size) = preceded( tag("$ ls\n"), fold_many0( // Map many newline-terminated entries terminated( // of either alt(( // A size followed by a name map(terminated(u32, take_until("\n")), Entry::File), // Or the word "dir" followed by a name map(preceded(tag("dir "), take_until("\n")), Entry::Dir), )), newline, ), || 0u32, |files_sum, entry| match entry { Entry::File(size) => files_sum + size, Entry::Dir(name) => { dir_stack.push(name); files_sum } }, ), )(input)?; for i in initial_len..dir_stack.len() { let (new_input, content_size) = delimited( tuple((tag("$ cd "), tag(dir_stack[i]), newline)), |input| parse_dir(input, dirs, dir_stack), // Optional cd'ing out because the last directory is never exited. opt(tag("$ cd ..\n")), )(input)?; input = new_input; size += content_size; } dirs.push(size); dir_stack.truncate(initial_len); Ok((input, size)) } fn parse_program(input: &[u8]) -> IResult<&[u8], (u32, Vec)> { let mut dirs = Vec::new(); let mut dirstack = Vec::new(); let (input, size) = preceded(tag("$ cd /\n"), |input| { parse_dir(input, &mut dirs, &mut dirstack) })(input)?; Ok((input, (size, dirs))) } pub fn part1(input: &[u8]) -> Result { let (_, sizes) = parse_input(input, parse_program)?; let searched_size: u32 = sizes.into_iter().filter(|&size| size <= 100_000).sum(); Ok(searched_size.to_string()) } pub fn part2(input: &[u8]) -> Result { const TARGET: u32 = 30_000_000; const TOTAL: u32 = 70_000_000; let (used, sizes) = parse_input(input, parse_program)?; let required = TARGET - (TOTAL - used); let min = sizes .into_iter() .filter(|&size| size >= required) .min() .context("Did not find dir large enough to delete")?; Ok(min.to_string()) } #[cfg(test)] mod tests { use super::*; const SAMPLE: &[u8] = include_bytes!("samples/07.txt"); #[test] fn sample_part1() { assert_eq!(part1(SAMPLE).unwrap(), "95437"); } #[test] fn sample_part2() { assert_eq!(part2(SAMPLE).unwrap(), "24933642"); } }