Skip to content

Commit 2b96e71

Browse files
committed
feat: add tree pruning for budget challenge
1 parent 572feca commit 2b96e71

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

src/challenge/challenge_07.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//! Challenge 07: Tree Pruning for Budget
2+
use anyhow::Result;
3+
use colored::Colorize;
4+
5+
use crate::utils::split_whitespace_and_parse;
6+
7+
#[derive(Clone)]
8+
struct Node {
9+
value: u32,
10+
cost: u32,
11+
children: Vec<usize>,
12+
}
13+
14+
impl Node {
15+
fn new(value: u32, cost: u32, children: Vec<usize>) -> Self {
16+
Self {
17+
value,
18+
cost,
19+
children,
20+
}
21+
}
22+
}
23+
24+
pub fn run(input: &str, output: Option<&str>) -> Result<String> {
25+
let mut lines = input.lines();
26+
let first_line = split_whitespace_and_parse::<u32>(lines.next().unwrap())?;
27+
28+
let n = first_line[0];
29+
let budget = first_line[1];
30+
31+
let mut tree = vec![Node::new(0, 0, vec![]); n as usize];
32+
33+
for _ in 0..n {
34+
let parts = split_whitespace_and_parse::<u32>(lines.next().unwrap())?;
35+
let id = parts[0] as usize - 1;
36+
let value = parts[1];
37+
let cost = parts[2];
38+
39+
tree[id] = Node::new(value, cost, vec![]);
40+
}
41+
42+
for _ in 0..(n - 1) {
43+
let parts = split_whitespace_and_parse::<usize>(lines.next().unwrap())?;
44+
let parent_id = parts[0] - 1;
45+
let child_id = parts[1] - 1;
46+
47+
tree[parent_id].children.push(child_id);
48+
}
49+
50+
let result = max_value_within_budget(&tree, 0, budget);
51+
52+
if let Some(output) = output {
53+
let expected = output.trim().parse::<u32>()?;
54+
55+
anyhow::ensure!(
56+
result == expected,
57+
"Output does not match expected: got '{result}' expected '{expected}'",
58+
);
59+
60+
return Ok(format!("Result matches the specified output: {}", result));
61+
}
62+
63+
Ok(format!(
64+
"Calculated the maximum value: {result}, but {} output!",
65+
"missing".red().bold()
66+
))
67+
}
68+
69+
fn max_value_within_budget(tree: &[Node], root_id: usize, budget: u32) -> u32 {
70+
let mut memo = vec![vec![None; budget as usize + 1]; tree.len()];
71+
dfs(tree, root_id, budget, &mut memo)
72+
}
73+
74+
fn dfs(tree: &[Node], node_id: usize, remaining_budget: u32, memo: &mut [Vec<Option<u32>>]) -> u32 {
75+
let node = &tree[node_id];
76+
77+
// If we've already computed this state, return memoized result
78+
if let Some(result) = memo[node_id][remaining_budget as usize] {
79+
return result;
80+
}
81+
82+
// If the node cost exceeds remaining budget, we can't keep this node or its subtree
83+
if node.cost > remaining_budget {
84+
memo[node_id][remaining_budget as usize] = Some(0);
85+
return 0;
86+
}
87+
88+
let budget_after_node = remaining_budget - node.cost;
89+
let mut value_with_node = node.value;
90+
91+
// For each child, compute the maximum value we can get
92+
for &child_id in &node.children {
93+
let child_value = dfs(tree, child_id, budget_after_node, memo);
94+
value_with_node += child_value;
95+
}
96+
97+
memo[node_id][remaining_budget as usize] = Some(value_with_node);
98+
value_with_node
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use super::*;
104+
105+
fn sample_tree() -> (Vec<Node>, u32) {
106+
let tree = vec![
107+
Node::new(5, 3, vec![1, 2]), // Node 1 (0-indexed)
108+
Node::new(2, 1, vec![]), // Node 2 (0-indexed)
109+
Node::new(8, 4, vec![]), // Node 3 (0-indexed)
110+
];
111+
(tree, 5)
112+
}
113+
114+
#[test]
115+
fn test_sample() {
116+
let (tree, budget) = sample_tree();
117+
assert_eq!(max_value_within_budget(&tree, 0, budget), 7);
118+
}
119+
120+
#[test]
121+
fn test_root_too_expensive() {
122+
let tree = vec![Node::new(5, 10, vec![1]), Node::new(2, 1, vec![])];
123+
assert_eq!(max_value_within_budget(&tree, 0, 5), 0);
124+
}
125+
126+
#[test]
127+
fn test_all_within_budget() {
128+
let tree = vec![Node::new(1, 1, vec![1]), Node::new(2, 1, vec![])];
129+
assert_eq!(max_value_within_budget(&tree, 0, 2), 3);
130+
}
131+
132+
#[test]
133+
fn test_large_budget() {
134+
let tree = vec![
135+
Node::new(10, 2, vec![1, 2]),
136+
Node::new(20, 3, vec![]),
137+
Node::new(30, 4, vec![]),
138+
];
139+
assert_eq!(max_value_within_budget(&tree, 0, 9), 60);
140+
}
141+
}

src/challenge/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod challenge_03;
44
mod challenge_04;
55
mod challenge_05;
66
mod challenge_06;
7+
mod challenge_07;
78

89
pub fn name(challenge: &str) -> Option<&'static str> {
910
match challenge {
@@ -13,6 +14,7 @@ pub fn name(challenge: &str) -> Option<&'static str> {
1314
"04" => Some("Bananas! Bananas! Bananas!"),
1415
"05" => Some("Anagramic Subsequence"),
1516
"06" => Some("Research Lab Recorder"),
17+
"07" => Some("Tree Pruning for Budget"),
1618
_ => None,
1719
}
1820
}
@@ -25,6 +27,7 @@ pub fn run(challenge: &str, input: &str, output: Option<&str>) -> anyhow::Result
2527
"04" => challenge_04::run(input, output),
2628
"05" => challenge_05::run(input, output),
2729
"06" => challenge_06::run(input, output),
30+
"07" => challenge_07::run(input, output),
2831
_ => anyhow::bail!("Error: Unknown challenge '{challenge}'"),
2932
}
3033
}

0 commit comments

Comments
 (0)