From a78c7ab0160e2853c6500694df2f1080c01fddd8 Mon Sep 17 00:00:00 2001 From: ictrobot Date: Tue, 29 Oct 2024 00:24:39 +0000 Subject: [PATCH] 2017 day 7 --- crates/year2017/examples/day07_example0.txt | 13 ++ crates/year2017/src/day07.rs | 143 ++++++++++++++++++++ crates/year2017/src/lib.rs | 1 + 3 files changed, 157 insertions(+) create mode 100644 crates/year2017/examples/day07_example0.txt create mode 100644 crates/year2017/src/day07.rs diff --git a/crates/year2017/examples/day07_example0.txt b/crates/year2017/examples/day07_example0.txt new file mode 100644 index 0000000..bb76880 --- /dev/null +++ b/crates/year2017/examples/day07_example0.txt @@ -0,0 +1,13 @@ +pbga (66) +xhth (57) +ebii (61) +havc (66) +ktlj (57) +fwft (72) -> ktlj, cntj, xhth +qoyq (66) +padx (45) -> pbga, havc, qoyq +tknk (41) -> ugml, padx, fwft +jptl (61) +ugml (68) -> gyxo, ebii, jptl +gyxo (61) +cntj (57) \ No newline at end of file diff --git a/crates/year2017/src/day07.rs b/crates/year2017/src/day07.rs new file mode 100644 index 0000000..e81c392 --- /dev/null +++ b/crates/year2017/src/day07.rs @@ -0,0 +1,143 @@ +use std::collections::HashMap; +use std::str; +use utils::prelude::*; + +/// Finding the unbalanced subtree. +#[derive(Clone, Debug)] +pub struct Day07<'a> { + programs: Vec>, + bottom: usize, +} + +#[derive(Clone, Debug)] +struct Program<'a> { + name: &'a [u8], + weight: u32, + parent: Option, + children: Vec, +} + +impl<'a> Day07<'a> { + pub fn new(input: &'a str, _: InputType) -> Result { + let name = parser::take_while1(u8::is_ascii_lowercase); + let lines = name + .with_suffix(" (") + .then(parser::u32().with_suffix(")")) + .then( + name.with_prefix(", ".optional()) + .repeat() + .with_prefix(" -> ".optional()), + ) + .parse_lines(input)?; + + let mut programs = lines + .iter() + .map(|&(name, weight, _)| Program { + name, + weight, + parent: None, + children: Vec::new(), + }) + .collect::>(); + + let name_map = lines + .iter() + .enumerate() + .map(|(index, &(name, _, _))| (name, index)) + .collect::>(); + + for (parent, (_, _, children)) in lines.into_iter().enumerate() { + // Use into_iter so that the children Vec<&[u8]> can be reused as the children + // Vec, avoiding an extra allocation and free per input line. + let children = children + .into_iter() + .map(|name| { + if let Some(&child) = name_map.get(name) { + programs[child].parent = Some(parent); + Ok(child) + } else { + Err(InputError::new( + input, + 0, + format!("program {:?} missing on LHS", str::from_utf8(name).unwrap()), + )) + } + }) + .collect::, _>>()?; + programs[parent].children = children; + } + + let Some(bottom) = programs.iter().position(|p| p.parent.is_none()) else { + return Err(InputError::new( + input, + 0, + "expected one program to have no parent", + )); + }; + + Ok(Self { programs, bottom }) + } + + #[must_use] + pub fn part1(&self) -> &str { + str::from_utf8(self.programs[self.bottom].name).unwrap() + } + + #[must_use] + pub fn part2(&self) -> u32 { + self.check(self.bottom).expect_err("tower is balanced") + } + + fn check(&self, index: usize) -> Result { + let program = &self.programs[index]; + if program.children.is_empty() { + // Programs with no children are always balanced. + Ok(program.weight) + } else if program.children.len() < 3 { + // Programs with one child are balanced as there aren't multiple sub-towers to disagree. + // Programs with two children must also be balanced as it is impossible to tell which + // sub-tower is wrong if you only have two different values. + let first_weight = self.check(program.children[0])?; + let all_children = first_weight * program.children.len() as u32; + Ok(program.weight + all_children) + } else { + let first_weight = self.check(program.children[0])?; + let mut first_matches = 0; + let mut second_weight = None; + for &child in &program.children[1..] { + let weight = self.check(child)?; + if weight == first_weight { + first_matches += 1; + } else if second_weight.is_none() { + second_weight = Some((weight, child)); + } else if second_weight.unwrap().0 != weight { + panic!( + "program {:?} has children with 3 different weights", + str::from_utf8(program.name).unwrap() + ); + } + } + + let Some((second_weight, second_index)) = second_weight else { + // All children match, this sub-tower is balanced + let all_children = first_weight * program.children.len() as u32; + return Ok(program.weight + all_children); + }; + + // Found the unbalanced sub-tower + let (correct_weight, wrong_weight, wrong_index) = if first_matches == 0 { + // First child wrong + (second_weight, first_weight, program.children[0]) + } else { + // Second weight wrong + (first_weight, second_weight, second_index) + }; + + Err(correct_weight + self.programs[wrong_index].weight - wrong_weight) + } + } +} + +examples!(Day07<'_> -> (&'static str, u32) [ + {file: "day07_example0.txt", part1: "tknk", part2: 60}, +]); diff --git a/crates/year2017/src/lib.rs b/crates/year2017/src/lib.rs index 33c1e32..90e46a9 100644 --- a/crates/year2017/src/lib.rs +++ b/crates/year2017/src/lib.rs @@ -8,4 +8,5 @@ utils::year!(2017 => year2017, ${ 4 => day04::Day04<'_>, 5 => day05::Day05, 6 => day06::Day06, + 7 => day07::Day07<'_>, });