1use std::collections::HashMap;
2use std::str;
3use utils::prelude::*;
4
5#[derive(Clone, Debug)]
7pub struct Day07<'a> {
8 programs: Vec<Program<'a>>,
9 bottom: usize,
10}
11
12#[derive(Clone, Debug)]
13struct Program<'a> {
14 name: &'a [u8],
15 weight: u32,
16 parent: Option<usize>,
17 children: Vec<usize>,
18}
19
20impl<'a> Day07<'a> {
21 pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
22 let name = parser::take_while1(u8::is_ascii_lowercase);
23 let lines = name
24 .with_suffix(" (")
25 .then(parser::u32().with_suffix(")"))
26 .then(name.repeat(", ", 0).with_prefix(" -> ".optional()))
27 .parse_lines(input)?;
28
29 let mut programs = lines
30 .iter()
31 .map(|&(name, weight, _)| Program {
32 name,
33 weight,
34 parent: None,
35 children: Vec::new(),
36 })
37 .collect::<Vec<_>>();
38
39 let name_map = lines
40 .iter()
41 .enumerate()
42 .map(|(index, &(name, _, _))| (name, index))
43 .collect::<HashMap<_, _>>();
44
45 for (parent, (_, _, children)) in lines.into_iter().enumerate() {
46 let children = children
49 .into_iter()
50 .map(|name| {
51 if let Some(&child) = name_map.get(name) {
52 programs[child].parent = Some(parent);
53 Ok(child)
54 } else {
55 Err(InputError::new(
56 input,
57 0,
58 format!("program {:?} missing on LHS", str::from_utf8(name).unwrap()),
59 ))
60 }
61 })
62 .collect::<Result<Vec<_>, _>>()?;
63 programs[parent].children = children;
64 }
65
66 let Some(bottom) = programs.iter().position(|p| p.parent.is_none()) else {
67 return Err(InputError::new(
68 input,
69 0,
70 "expected one program to have no parent",
71 ));
72 };
73
74 Ok(Self { programs, bottom })
75 }
76
77 #[must_use]
78 pub fn part1(&self) -> &str {
79 str::from_utf8(self.programs[self.bottom].name).unwrap()
80 }
81
82 #[must_use]
83 pub fn part2(&self) -> u32 {
84 self.check(self.bottom).expect_err("tower is balanced")
85 }
86
87 fn check(&self, index: usize) -> Result<u32, u32> {
88 let program = &self.programs[index];
89 if program.children.is_empty() {
90 Ok(program.weight)
92 } else if program.children.len() < 3 {
93 let first_weight = self.check(program.children[0])?;
97 let all_children = first_weight * program.children.len() as u32;
98 Ok(program.weight + all_children)
99 } else {
100 let first_weight = self.check(program.children[0])?;
101 let mut first_matches = 0;
102 let mut second_weight = None;
103 for &child in &program.children[1..] {
104 let weight = self.check(child)?;
105 if weight == first_weight {
106 first_matches += 1;
107 } else if second_weight.is_none() {
108 second_weight = Some((weight, child));
109 } else if second_weight.unwrap().0 != weight {
110 panic!(
111 "program {:?} has children with 3 different weights",
112 str::from_utf8(program.name).unwrap()
113 );
114 }
115 }
116
117 let Some((second_weight, second_index)) = second_weight else {
118 let all_children = first_weight * program.children.len() as u32;
120 return Ok(program.weight + all_children);
121 };
122
123 let (correct_weight, wrong_weight, wrong_index) = if first_matches == 0 {
125 (second_weight, first_weight, program.children[0])
127 } else {
128 (first_weight, second_weight, second_index)
130 };
131
132 Err(correct_weight + self.programs[wrong_index].weight - wrong_weight)
133 }
134 }
135}
136
137examples!(Day07<'_> -> (&'static str, u32) [
138 {file: "day07_example0.txt", part1: "tknk", part2: 60},
139]);