Skip to content

Commit efac625

Browse files
committed
example refactoring and documentation for mutable traversal
1 parent b4e2359 commit efac625

File tree

1 file changed

+135
-57
lines changed

1 file changed

+135
-57
lines changed

examples/mutable_traversal.rs

Lines changed: 135 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1+
// # EXAMPLE DEFINITION
2+
//
3+
// This example demonstrates a use case where value of a node is defined
4+
// as a function of the values of its children. Since the value of a child
5+
// of the node also depends on values of its own children, it follows that
6+
// the value of a node is a function of values of all of its descendants.
7+
//
8+
// The task is to compute and set all values of a tree given the values of
9+
// the leaves.
10+
//
11+
// This is a interesting and common case in terms of requiring mutable
12+
// recursive traversal over the tree that can be handled with different
13+
// approaches. Some of these are demonstrated in this example.
14+
115
use orx_tree::*;
216
use std::fmt::Display;
317

4-
const INPUTS_COUNT: usize = 2;
5-
618
#[derive(Debug, Clone, Copy)]
719
enum Instruction {
820
Input(usize),
@@ -43,11 +55,11 @@ impl Display for InstructionNode {
4355
}
4456

4557
#[derive(Debug)]
46-
struct MyTree {
58+
struct Instructions {
4759
tree: DynTree<InstructionNode>,
4860
}
4961

50-
impl MyTree {
62+
impl Instructions {
5163
fn example() -> Self {
5264
let mut tree = DynTree::new(InstructionNode::new(Instruction::AddI { val: 100.0 }, 0.0));
5365

@@ -66,32 +78,32 @@ impl MyTree {
6678

6779
Self { tree }
6880
}
69-
70-
fn execute_rec(&mut self, inputs: &[f32], node_idx: NodeIdx<Dyn<InstructionNode>>) -> f32 {
71-
let node = self.tree.node(&node_idx);
72-
73-
let children_ids = node.children().map(|child| child.idx()).collect::<Vec<_>>();
74-
let mut children = vec![];
75-
76-
for node in children_ids {
77-
let value = self.execute_rec(inputs, node);
78-
children.push(value);
79-
}
80-
81-
let mut node = self.tree.node_mut(&node_idx);
82-
83-
let new_value = match node.data().instruction {
84-
Instruction::Input(i) => inputs[i],
85-
Instruction::Add => children.into_iter().sum(),
86-
Instruction::AddI { val } => children.into_iter().sum::<f32>() + val,
87-
};
88-
(*node.data_mut()).value = new_value;
89-
90-
new_value
91-
}
9281
}
9382

94-
fn execute<'a>(
83+
/// Demonstrates manual mutable and recursive traversal over the tree.
84+
///
85+
/// Notice that we can freely walk the tree while always having a single
86+
/// mutable reference to one node. This satisfies the borrow checker rules
87+
/// and further allows for calling the function recursively.
88+
///
89+
/// Note also that, although it is not necessary in this scenario, we are
90+
/// free to change the shape of the tree during our walk by adding nodes,
91+
/// moving around or pruning subtrees, etc. In other words, it enables the
92+
/// greatest freedom while it requires us to make sure that we do not have
93+
/// errors, such as out-of-bounds errors with the `into_child_mut` call.
94+
///
95+
/// * Pros
96+
/// * Complete freedom to mutate the nodes and the tree structure during
97+
/// the walk.
98+
/// * No intermediate allocation is required; borrow checker rules are
99+
/// satisfied without the need to collect indices.
100+
/// * Cons
101+
/// * Implementor is required to define the walk. This example demonstrates
102+
/// a depth-first walk due to the recursive calls, which is straightforward
103+
/// to implement.
104+
/// * Due to lack of tail-call optimization in rust, this function is likely
105+
/// to encounter stack overflow for very deep trees.
106+
fn recursive_traversal_over_nodes<'a>(
95107
inputs: &[f32],
96108
mut node: NodeMut<'a, Dyn<InstructionNode>>,
97109
) -> (NodeMut<'a, Dyn<InstructionNode>>, f32) {
@@ -100,7 +112,7 @@ fn execute<'a>(
100112
let mut children_sum = 0.0;
101113
for i in 0..num_children {
102114
let child = node.into_child_mut(i).unwrap();
103-
let (child, child_value) = execute(inputs, child);
115+
let (child, child_value) = recursive_traversal_over_nodes(inputs, child);
104116
children_sum += child_value;
105117
node = child.into_parent_mut().unwrap();
106118
}
@@ -116,43 +128,109 @@ fn execute<'a>(
116128
(node, new_value)
117129
}
118130

119-
fn test_implementation(method: &str, f: impl FnOnce(&[f32], &mut MyTree)) {
120-
let inputs = [10.0, 20.0];
131+
/// Demonstrates recursive mutable traversal by internally collecting and storing
132+
/// the child node indices.
133+
///
134+
/// This simplifies the borrow relations and allows for the recursive calls only
135+
/// having a single mutable reference to the tree; however, each recursive call
136+
/// requires an internal allocation.
137+
///
138+
/// * Pros
139+
/// * Complete freedom to mutate the nodes and the tree structure during
140+
/// the walk.
141+
/// * Cons
142+
/// * Requires to collect indices and results into an internal vector for each
143+
/// recursive call, requiring additional allocation.
144+
/// * Implementor is required to define the walk. This example demonstrates
145+
/// a depth-first walk due to the recursive calls, which is straightforward
146+
/// to implement.
147+
/// * Due to lack of tail-call optimization in rust, this function is likely
148+
/// to encounter stack overflow for very deep trees.
149+
fn recursive_traversal_over_indices(
150+
tree: &mut DynTree<InstructionNode>,
151+
inputs: &[f32],
152+
node_idx: NodeIdx<Dyn<InstructionNode>>,
153+
) -> f32 {
154+
let node = tree.node(&node_idx);
155+
156+
let children_ids: Vec<_> = node.children().map(|child| child.idx()).collect();
157+
let children: Vec<_> = children_ids
158+
.into_iter()
159+
.map(|node| recursive_traversal_over_indices(tree, inputs, node))
160+
.collect();
121161

122-
let mut tree = MyTree::example();
162+
let mut node = tree.node_mut(&node_idx);
123163

124-
println!("\n\n# {}", method);
125-
println!("\ninputs = {:?}\n", &inputs);
126-
println!("Before execute:\n{}\n", &tree.tree);
127-
f(&inputs, &mut tree);
128-
println!("After execute:\n{}\n", &tree.tree);
164+
let new_value = match node.data().instruction {
165+
Instruction::Input(i) => inputs[i],
166+
Instruction::Add => children.into_iter().sum(),
167+
Instruction::AddI { val } => children.into_iter().sum::<f32>() + val,
168+
};
169+
(*node.data_mut()).value = new_value;
170+
171+
new_value
129172
}
130173

131-
fn main() {
132-
test_implementation("IMPL OVER CHILDREN INDICES", |inputs, tree| {
133-
tree.execute_rec(inputs, tree.tree.root().idx());
174+
/// Demonstrates the use of [`recursive_set`] method:
175+
///
176+
/// *Recursively sets the data of all nodes belonging to the subtree rooted
177+
/// at this node using the compute_data function.*
178+
///
179+
/// This function fits perfectly to this and similar scenarios where we want
180+
/// to compute values of all nodes of a tree such that the value of a node
181+
/// depends on the values of all of its descendants, and hence the name
182+
/// *recursive*.
183+
///
184+
/// * Pros
185+
/// * More expressive in the sense that the implementor only defines how the
186+
/// value of a node should be computed given its prior value and values of
187+
/// its children. Iteration is abstracted away.
188+
/// * Despite the name, the implementation actually does not require recursive
189+
/// function calls; and hence, can work with trees of arbitrary depth without
190+
/// the risk of stack overflow. Instead, it internally uses the [`PostOrder`]
191+
/// traverser.
192+
/// * Cons
193+
/// * It only allows to set the data of the nodes; however, does not allow for
194+
/// structural mutations.
195+
///
196+
/// [`recursive_set`]: orx_tree::NodeMut::recursive_set
197+
/// [`PostOrder`]: orx_tree::PostOrder
198+
fn recursive_set(inputs: &[f32], mut node: NodeMut<Dyn<InstructionNode>>) {
199+
node.recursive_set(|node_data, children_data| {
200+
let instruction = node_data.instruction;
201+
let children_sum: f32 = children_data.iter().map(|x| x.value).sum();
202+
let value = match node_data.instruction {
203+
Instruction::Input(i) => inputs[i],
204+
Instruction::Add => children_sum,
205+
Instruction::AddI { val } => val + children_sum,
206+
};
207+
208+
InstructionNode { instruction, value }
134209
});
210+
}
211+
212+
fn main() {
213+
fn test_implementation(method: &str, f: impl FnOnce(&[f32], &mut Instructions)) {
214+
let inputs = [10.0, 20.0];
215+
let mut instructions = Instructions::example();
216+
println!("\n\n### {}", method);
217+
f(&inputs, &mut instructions);
218+
println!("\n{}\n", &instructions.tree);
219+
}
135220

136221
test_implementation(
137-
"IMPL WITH INTO_CHILD_MUT & INTO_PARENT_MUT",
138-
|inputs, tree| {
139-
execute(&inputs, tree.tree.root_mut());
222+
"recursive_traversal_over_indices",
223+
|inputs, instructions| {
224+
let root_idx = instructions.tree.root().idx();
225+
recursive_traversal_over_indices(&mut instructions.tree, inputs, root_idx);
140226
},
141227
);
142228

143-
test_implementation("IMPL recursive_set", |inputs, tree| {
144-
tree.tree
145-
.root_mut()
146-
.recursive_set(|node_data, children_data| {
147-
let instruction = node_data.instruction;
148-
let children_sum: f32 = children_data.iter().map(|x| x.value).sum();
149-
let value = match node_data.instruction {
150-
Instruction::Input(i) => inputs[i],
151-
Instruction::Add => children_sum,
152-
Instruction::AddI { val } => val + children_sum,
153-
};
154-
155-
InstructionNode { instruction, value }
156-
});
229+
test_implementation("recursive_traversal_over_nodes", |inputs, instructions| {
230+
recursive_traversal_over_nodes(&inputs, instructions.tree.root_mut());
231+
});
232+
233+
test_implementation("recursive_set", |inputs, instructions| {
234+
recursive_set(inputs, instructions.tree.root_mut());
157235
});
158236
}

0 commit comments

Comments
 (0)