Skip to content

Commit a78c7ab

Browse files
committed
2017 day 7
1 parent 88af3ba commit a78c7ab

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pbga (66)
2+
xhth (57)
3+
ebii (61)
4+
havc (66)
5+
ktlj (57)
6+
fwft (72) -> ktlj, cntj, xhth
7+
qoyq (66)
8+
padx (45) -> pbga, havc, qoyq
9+
tknk (41) -> ugml, padx, fwft
10+
jptl (61)
11+
ugml (68) -> gyxo, ebii, jptl
12+
gyxo (61)
13+
cntj (57)

crates/year2017/src/day07.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use std::collections::HashMap;
2+
use std::str;
3+
use utils::prelude::*;
4+
5+
/// Finding the unbalanced subtree.
6+
#[derive(Clone, Debug)]
7+
pub struct Day07<'a> {
8+
programs: Vec<Program<'a>>,
9+
bottom: usize,
10+
}
11+
12+
#[derive(Clone, Debug)]
13+
struct Program<'a> {
14+
name: &'a [u8],
15+
weight: u32,
16+
parent: Option<usize>,
17+
children: Vec<usize>,
18+
}
19+
20+
impl<'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(
27+
name.with_prefix(", ".optional())
28+
.repeat()
29+
.with_prefix(" -> ".optional()),
30+
)
31+
.parse_lines(input)?;
32+
33+
let mut programs = lines
34+
.iter()
35+
.map(|&(name, weight, _)| Program {
36+
name,
37+
weight,
38+
parent: None,
39+
children: Vec::new(),
40+
})
41+
.collect::<Vec<_>>();
42+
43+
let name_map = lines
44+
.iter()
45+
.enumerate()
46+
.map(|(index, &(name, _, _))| (name, index))
47+
.collect::<HashMap<_, _>>();
48+
49+
for (parent, (_, _, children)) in lines.into_iter().enumerate() {
50+
// Use into_iter so that the children Vec<&[u8]> can be reused as the children
51+
// Vec<usize>, avoiding an extra allocation and free per input line.
52+
let children = children
53+
.into_iter()
54+
.map(|name| {
55+
if let Some(&child) = name_map.get(name) {
56+
programs[child].parent = Some(parent);
57+
Ok(child)
58+
} else {
59+
Err(InputError::new(
60+
input,
61+
0,
62+
format!("program {:?} missing on LHS", str::from_utf8(name).unwrap()),
63+
))
64+
}
65+
})
66+
.collect::<Result<Vec<_>, _>>()?;
67+
programs[parent].children = children;
68+
}
69+
70+
let Some(bottom) = programs.iter().position(|p| p.parent.is_none()) else {
71+
return Err(InputError::new(
72+
input,
73+
0,
74+
"expected one program to have no parent",
75+
));
76+
};
77+
78+
Ok(Self { programs, bottom })
79+
}
80+
81+
#[must_use]
82+
pub fn part1(&self) -> &str {
83+
str::from_utf8(self.programs[self.bottom].name).unwrap()
84+
}
85+
86+
#[must_use]
87+
pub fn part2(&self) -> u32 {
88+
self.check(self.bottom).expect_err("tower is balanced")
89+
}
90+
91+
fn check(&self, index: usize) -> Result<u32, u32> {
92+
let program = &self.programs[index];
93+
if program.children.is_empty() {
94+
// Programs with no children are always balanced.
95+
Ok(program.weight)
96+
} else if program.children.len() < 3 {
97+
// Programs with one child are balanced as there aren't multiple sub-towers to disagree.
98+
// Programs with two children must also be balanced as it is impossible to tell which
99+
// sub-tower is wrong if you only have two different values.
100+
let first_weight = self.check(program.children[0])?;
101+
let all_children = first_weight * program.children.len() as u32;
102+
Ok(program.weight + all_children)
103+
} else {
104+
let first_weight = self.check(program.children[0])?;
105+
let mut first_matches = 0;
106+
let mut second_weight = None;
107+
for &child in &program.children[1..] {
108+
let weight = self.check(child)?;
109+
if weight == first_weight {
110+
first_matches += 1;
111+
} else if second_weight.is_none() {
112+
second_weight = Some((weight, child));
113+
} else if second_weight.unwrap().0 != weight {
114+
panic!(
115+
"program {:?} has children with 3 different weights",
116+
str::from_utf8(program.name).unwrap()
117+
);
118+
}
119+
}
120+
121+
let Some((second_weight, second_index)) = second_weight else {
122+
// All children match, this sub-tower is balanced
123+
let all_children = first_weight * program.children.len() as u32;
124+
return Ok(program.weight + all_children);
125+
};
126+
127+
// Found the unbalanced sub-tower
128+
let (correct_weight, wrong_weight, wrong_index) = if first_matches == 0 {
129+
// First child wrong
130+
(second_weight, first_weight, program.children[0])
131+
} else {
132+
// Second weight wrong
133+
(first_weight, second_weight, second_index)
134+
};
135+
136+
Err(correct_weight + self.programs[wrong_index].weight - wrong_weight)
137+
}
138+
}
139+
}
140+
141+
examples!(Day07<'_> -> (&'static str, u32) [
142+
{file: "day07_example0.txt", part1: "tknk", part2: 60},
143+
]);

crates/year2017/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ utils::year!(2017 => year2017, ${
88
4 => day04::Day04<'_>,
99
5 => day05::Day05,
1010
6 => day06::Day06,
11+
7 => day07::Day07<'_>,
1112
});

0 commit comments

Comments
 (0)