advent_of_code/year2017/
day07.rs1use crate::input::Input;
2use std::collections::{HashMap, HashSet};
3
4type ProgramId = usize;
5
6#[derive(Clone)]
7struct Program<'a> {
8 name: &'a str,
9 weight: u32,
10 children: Vec<ProgramId>,
11}
12
13struct ProgramTree<'a> {
14 nodes: Vec<Program<'a>>,
15 root_node: ProgramId,
16}
17
18impl ProgramTree<'_> {
19 fn total_weight(&self, program_id: ProgramId) -> u32 {
20 let program = &self.nodes[program_id];
21 program.weight
22 + program
23 .children
24 .iter()
25 .map(|&child| self.total_weight(child))
26 .sum::<u32>()
27 }
28}
29
30impl<'a> ProgramTree<'a> {
31 fn parse(input_string: &'a str) -> Result<Self, String> {
32 let mut nodes = Vec::new();
33 let mut name_to_node: HashMap<&str, ProgramId> = HashMap::new();
34
35 for (line_index, line) in input_string.lines().enumerate() {
36 let parts: Vec<&str> = line.split(" -> ").collect();
37 let (name, weight_str) = parts[0].split_once(' ').ok_or_else(|| {
38 format!(
39 "Line {}: Invalid format, expected '$NAME ($WEIGHT)'",
40 line_index + 1
41 )
42 })?;
43 let weight = weight_str
44 .replace(['(', ')'], "")
45 .parse::<u32>()
46 .map_err(|error| format!("Line {}: Invalid weight ({})", line_index + 1, error))?;
47
48 let program_id = nodes.len();
49 let program = Program {
50 name,
51 weight,
52 children: Vec::new(),
53 };
54 nodes.push(program);
55 name_to_node.insert(name, program_id);
56 }
57
58 for line in input_string.lines() {
59 let parts: Vec<&str> = line.split(" -> ").collect();
60 let name_weight_parts: Vec<&str> = parts[0].split(' ').collect();
61 if parts.len() == 2 {
62 let name = name_weight_parts[0];
63 let parent = *name_to_node
64 .get_mut(name)
65 .ok_or("Internal error - no node for name")?;
66 for child_name in parts[1].trim().split(", ") {
67 let child_ref = *name_to_node
68 .get(child_name)
69 .ok_or("Invalid input - no node for name")?;
70 nodes[parent].children.push(child_ref);
71 }
72 }
73 }
74
75 let all_program_ids: HashSet<&ProgramId> = name_to_node.values().collect();
76 let children: HashSet<&ProgramId> = name_to_node
77 .values()
78 .flat_map(|&child_program_id| nodes[child_program_id].children.iter())
79 .collect();
80
81 let roots: Vec<&&ProgramId> = all_program_ids.difference(&children).collect();
82 let root_node = **roots[0];
83 if roots.len() == 1 {
84 Ok(Self { nodes, root_node })
85 } else {
86 Err("No single root found".to_string())
87 }
88 }
89}
90
91pub fn solve(input: &Input) -> Result<String, String> {
92 let tree = ProgramTree::parse(input.text)?;
93 if input.is_part_one() {
94 Ok(tree.nodes[tree.root_node].name.to_string())
95 } else {
96 fixup_weight(tree.root_node, &tree)
97 .map(|value| value.to_string())
98 .ok_or_else(|| "No solution found".to_string())
99 }
100}
101
102fn fixup_weight(program_id: ProgramId, tree: &ProgramTree) -> Option<u32> {
103 let program = &tree.nodes[program_id];
104 if program.children.len() > 1 {
105 if let (Some(lone_weight), desired_weight) = program
106 .children
107 .iter()
108 .fold(HashMap::new(), |mut acc, &child_id| {
109 *acc.entry(tree.total_weight(child_id)).or_insert(0) += 1;
110 acc
111 })
112 .iter()
113 .fold((None, 0), |acc, (&weight, &occurrences)| {
114 if occurrences == 1 {
115 (Some(weight), acc.1)
116 } else {
117 (acc.0, weight)
118 }
119 })
120 {
121 if let Some(&child_id) = program
122 .children
123 .iter()
124 .find(|&&p| tree.total_weight(p) == lone_weight)
125 {
126 return fixup_weight(child_id, tree).or_else(|| {
127 let total_weight = tree.total_weight(child_id);
128 let child = &tree.nodes[child_id];
129 Some(desired_weight - (total_weight - child.weight))
130 });
131 }
132 }
133 } else if !program.children.is_empty() {
134 if let Some(value) = fixup_weight(program.children[0], tree) {
135 return Some(value);
136 }
137 }
138 None
139}
140
141#[test]
142fn tests() {
143 use crate::input::{test_part_one, test_part_two};
144 let real_input = include_str!("day07_input.txt");
145 test_part_one!(real_input => "veboyvy".to_string());
146 test_part_two!(real_input => "749".to_string());
147}