Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 847af89

Browse files
authoredApr 27, 2025··
Merge pull request #160 from orxfun/mutable-traversal
mutable recursive traversal and recursive_set method
2 parents f7ccd30 + 3fa41de commit 847af89

File tree

5 files changed

+415
-2
lines changed

5 files changed

+415
-2
lines changed
 

‎Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "orx-tree"
3-
version = "1.4.0"
3+
version = "1.5.0"
44
edition = "2024"
55
authors = ["orxfun <orx.ugur.arikan@gmail.com>"]
66
description = "A beautiful tree 🌳 with convenient and efficient growth, mutation and traversal features."

‎README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ let remaining_bfs: Vec<_> = tree.root().walk::<Bfs>().copied().collect();
375375
assert_eq!(remaining_bfs, [1, 3, 6, 9]);
376376
```
377377

378+
### More Examples
379+
380+
* [mutable_recursive_traversal](https://github.com/orxfun/orx-tree/blob/main/examples/mutable_recursive_traversal.rs) demonstrates different approaches to achieve a recursive mutation of all nodes in the tree.
381+
378382
## Contributing
379383

380384
Contributions are welcome! If you notice an error, have a question or think something could be added or improved, please open an [issue](https://github.com/orxfun/orx-tree/issues/new) or create a PR.
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// # EXAMPLE DEFINITION
2+
//
3+
// cargo run --example mutable_recursive_traversal
4+
//
5+
// This example demonstrates a use case where value of a node is defined
6+
// as a function of the values of its children. Since the value of a child
7+
// of the node also depends on values of its own children, it follows that
8+
// the value of a node is a function of values of all of its descendants.
9+
//
10+
// The task is to compute and set all values of a tree given the values of
11+
// the leaves.
12+
//
13+
// This is a interesting and common case in terms of requiring mutable
14+
// recursive traversal over the tree that can be handled with different
15+
// approaches. Some of these are demonstrated in this example.
16+
17+
use orx_tree::*;
18+
use std::fmt::Display;
19+
20+
#[derive(Debug, Clone, Copy)]
21+
enum Instruction {
22+
Input(usize),
23+
Add,
24+
AddI { val: f32 },
25+
}
26+
27+
impl Display for Instruction {
28+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29+
match self {
30+
Self::Input(x) => write!(f, "Input({})", x),
31+
Self::Add => write!(f, "Add"),
32+
Self::AddI { val } => write!(f, "AddI({})", val),
33+
}
34+
}
35+
}
36+
37+
#[derive(Debug)]
38+
struct InstructionNode {
39+
instruction: Instruction,
40+
value: f32,
41+
}
42+
43+
impl InstructionNode {
44+
fn new(instruction: Instruction, value: f32) -> Self {
45+
Self { instruction, value }
46+
}
47+
}
48+
49+
impl Display for InstructionNode {
50+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51+
match &self.instruction {
52+
Instruction::Input(x) => write!(f, "Input({}) => {}", x, self.value),
53+
Instruction::Add => write!(f, "Add => {}", self.value),
54+
Instruction::AddI { val } => write!(f, "AddI({}) => {}", val, self.value),
55+
}
56+
}
57+
}
58+
59+
#[derive(Debug)]
60+
struct Instructions {
61+
tree: DynTree<InstructionNode>,
62+
}
63+
64+
impl Instructions {
65+
fn example() -> Self {
66+
let mut tree = DynTree::new(InstructionNode::new(Instruction::AddI { val: 100.0 }, 0.0));
67+
68+
let mut n0 = tree.root_mut();
69+
let [n1, n2] = n0.push_children([
70+
InstructionNode::new(Instruction::Input(1), 0.0),
71+
InstructionNode::new(Instruction::AddI { val: 2.0 }, 0.0),
72+
]);
73+
let _n3 = tree
74+
.node_mut(&n1)
75+
.push_child(InstructionNode::new(Instruction::Input(0), 0.0));
76+
let [_n4, _n5] = tree.node_mut(&n2).push_children([
77+
InstructionNode::new(Instruction::Add, 0.0),
78+
InstructionNode::new(Instruction::AddI { val: 5.0 }, 0.0),
79+
]);
80+
81+
Self { tree }
82+
}
83+
}
84+
85+
/// Demonstrates manual mutable and recursive traversal over the tree.
86+
///
87+
/// Notice that we can freely walk the tree while always having a single
88+
/// mutable reference to one node. This satisfies the borrow checker rules
89+
/// and further allows for calling the function recursively.
90+
///
91+
/// Note also that, although it is not necessary in this scenario, we are
92+
/// free to change the shape of the tree during our walk by adding nodes,
93+
/// moving around or pruning subtrees, etc. In other words, it enables the
94+
/// greatest freedom while it requires us to make sure that we do not have
95+
/// errors, such as out-of-bounds errors with the `into_child_mut` call.
96+
///
97+
/// * Pros
98+
/// * Complete freedom to mutate the nodes and the tree structure during
99+
/// the walk.
100+
/// * No intermediate allocation is required; borrow checker rules are
101+
/// satisfied without the need to collect indices.
102+
/// * Cons
103+
/// * Implementor is required to define the walk. This example demonstrates
104+
/// a depth-first walk due to the recursive calls, which is straightforward
105+
/// to implement.
106+
/// * Due to lack of tail-call optimization in rust, this function is likely
107+
/// to encounter stack overflow for very deep trees.
108+
fn recursive_traversal_over_nodes<'a>(
109+
inputs: &[f32],
110+
mut node: NodeMut<'a, Dyn<InstructionNode>>,
111+
) -> (NodeMut<'a, Dyn<InstructionNode>>, f32) {
112+
let num_children = node.num_children();
113+
114+
let mut children_sum = 0.0;
115+
for i in 0..num_children {
116+
let child = node.into_child_mut(i).unwrap();
117+
let (child, child_value) = recursive_traversal_over_nodes(inputs, child);
118+
children_sum += child_value;
119+
node = child.into_parent_mut().unwrap();
120+
}
121+
122+
let new_value = match node.data().instruction {
123+
Instruction::Input(i) => inputs[i],
124+
Instruction::Add => children_sum,
125+
Instruction::AddI { val } => val + children_sum,
126+
};
127+
128+
(*node.data_mut()).value = new_value;
129+
130+
(node, new_value)
131+
}
132+
133+
/// Demonstrates recursive mutable traversal by internally collecting and storing
134+
/// the child node indices.
135+
///
136+
/// This simplifies the borrow relations and allows for the recursive calls only
137+
/// having a single mutable reference to the tree; however, each recursive call
138+
/// requires an internal allocation.
139+
///
140+
/// * Pros
141+
/// * Complete freedom to mutate the nodes and the tree structure during
142+
/// the walk.
143+
/// * Cons
144+
/// * Requires to collect indices and results into an internal vector for each
145+
/// recursive call, requiring additional allocation.
146+
/// * Implementor is required to define the walk. This example demonstrates
147+
/// a depth-first walk due to the recursive calls, which is straightforward
148+
/// to implement.
149+
/// * Due to lack of tail-call optimization in rust, this function is likely
150+
/// to encounter stack overflow for very deep trees.
151+
fn recursive_traversal_over_indices(
152+
tree: &mut DynTree<InstructionNode>,
153+
inputs: &[f32],
154+
node_idx: NodeIdx<Dyn<InstructionNode>>,
155+
) -> f32 {
156+
let node = tree.node(&node_idx);
157+
158+
let children_ids: Vec<_> = node.children().map(|child| child.idx()).collect();
159+
let children: Vec<_> = children_ids
160+
.into_iter()
161+
.map(|node| recursive_traversal_over_indices(tree, inputs, node))
162+
.collect();
163+
164+
let mut node = tree.node_mut(&node_idx);
165+
166+
let new_value = match node.data().instruction {
167+
Instruction::Input(i) => inputs[i],
168+
Instruction::Add => children.into_iter().sum(),
169+
Instruction::AddI { val } => children.into_iter().sum::<f32>() + val,
170+
};
171+
(*node.data_mut()).value = new_value;
172+
173+
new_value
174+
}
175+
176+
/// Demonstrates the use of [`recursive_set`] method:
177+
///
178+
/// *Recursively sets the data of all nodes belonging to the subtree rooted
179+
/// at this node using the compute_data function.*
180+
///
181+
/// This function fits perfectly to this and similar scenarios where we want
182+
/// to compute values of all nodes of a tree such that the value of a node
183+
/// depends on the values of all of its descendants, and hence the name
184+
/// *recursive*.
185+
///
186+
/// * Pros
187+
/// * More expressive in the sense that the implementor only defines how the
188+
/// value of a node should be computed given its prior value and values of
189+
/// its children. Iteration is abstracted away.
190+
/// * Despite the name, the implementation actually does not require recursive
191+
/// function calls; and hence, can work with trees of arbitrary depth without
192+
/// the risk of stack overflow. Instead, it internally uses the [`PostOrder`]
193+
/// traverser.
194+
/// * Cons
195+
/// * It only allows to set the data of the nodes; however, does not allow for
196+
/// structural mutations.
197+
///
198+
/// [`recursive_set`]: orx_tree::NodeMut::recursive_set
199+
/// [`PostOrder`]: orx_tree::PostOrder
200+
fn recursive_set(inputs: &[f32], mut node: NodeMut<Dyn<InstructionNode>>) {
201+
node.recursive_set(|node_data, children_data| {
202+
let instruction = node_data.instruction;
203+
let children_sum: f32 = children_data.iter().map(|x| x.value).sum();
204+
let value = match node_data.instruction {
205+
Instruction::Input(i) => inputs[i],
206+
Instruction::Add => children_sum,
207+
Instruction::AddI { val } => val + children_sum,
208+
};
209+
210+
InstructionNode { instruction, value }
211+
});
212+
}
213+
214+
fn main() {
215+
fn test_implementation(method: &str, f: impl FnOnce(&[f32], &mut Instructions)) {
216+
let inputs = [10.0, 20.0];
217+
let mut instructions = Instructions::example();
218+
println!("\n\n### {}", method);
219+
f(&inputs, &mut instructions);
220+
println!("\n{}\n", &instructions.tree);
221+
}
222+
223+
test_implementation(
224+
"recursive_traversal_over_indices",
225+
|inputs, instructions| {
226+
let root_idx = instructions.tree.root().idx();
227+
recursive_traversal_over_indices(&mut instructions.tree, inputs, root_idx);
228+
},
229+
);
230+
231+
test_implementation("recursive_traversal_over_nodes", |inputs, instructions| {
232+
recursive_traversal_over_nodes(&inputs, instructions.tree.root_mut());
233+
});
234+
235+
test_implementation("recursive_set", |inputs, instructions| {
236+
recursive_set(inputs, instructions.tree.root_mut());
237+
});
238+
}

‎src/node_mut.rs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
NodeIdx, NodeRef, SubTree, Traverser, Tree, TreeVariant,
2+
NodeIdx, NodeRef, PostOrder, SubTree, Traverser, Tree, TreeVariant,
33
aliases::{Col, N},
44
iter::ChildrenMutIter,
55
memory::{Auto, MemoryPolicy},
@@ -10,12 +10,15 @@ use crate::{
1010
traversal::{
1111
OverData, OverMut,
1212
enumerations::Val,
13+
over::OverPtr,
1314
over_mut::{OverItemInto, OverItemMut},
1415
post_order::iter_ptr::PostOrderIterPtr,
16+
traverser_core::TraverserCore,
1517
},
1618
tree_node_idx::INVALID_IDX_ERROR,
1719
tree_variant::RefsChildren,
1820
};
21+
use alloc::vec::Vec;
1922
use core::{fmt::Debug, marker::PhantomData};
2023
use orx_selfref_col::{NodePtr, Refs};
2124

@@ -2455,6 +2458,173 @@ where
24552458
traverser.into_iter(self)
24562459
}
24572460

2461+
// recursive
2462+
2463+
/// Recursively sets the data of all nodes belonging to the subtree rooted at this node using the `compute_data`
2464+
/// function.
2465+
///
2466+
/// This method provides an expressive way to update the values of a tree where value of a node is a function of
2467+
/// its prior value and values of its children. Since the values of its children subsequently depend on their own
2468+
/// children, it immediately follows that the value of the node depends on values of all of its descendants that
2469+
/// must be computed to be able to compute the node's value.
2470+
///
2471+
/// The `compute_data` function takes two arguments:
2472+
///
2473+
/// * current value (data) of this node, and
2474+
/// * slice of values of children of this node that are computed recursively using `compute_data` (*);
2475+
///
2476+
/// and then, computes the new value of this node.
2477+
///
2478+
/// The method is named *recursive* (*) due to the fact that,
2479+
///
2480+
/// * before computing the value of this node;
2481+
/// * values of all of its children are also computed and set using the `compute_data` function.
2482+
///
2483+
/// *Note that this method does **not** actually make recursive method calls. Instead, it internally uses the [`PostOrder`]
2484+
/// traverser which ensures that all required values are computed before they are used for another computation. This
2485+
/// is a guard against potential stack overflow issues, and hence, can be used for trees of arbitrary depth.*
2486+
///
2487+
/// [`PostOrder`]: crate::PostOrder
2488+
///
2489+
/// # Examples
2490+
///
2491+
/// In the following example, we set the value of every node to the sum of values of all its descendants.
2492+
///
2493+
/// While building the tree, we set only the values of the leaves.
2494+
/// We initially set values of all other nodes to zero as a placeholder.
2495+
/// Then, we call `recursive_set` to compute them.
2496+
///
2497+
/// ```
2498+
/// use orx_tree::*;
2499+
///
2500+
/// let mut tree = DynTree::<_>::new(0);
2501+
/// let [id1, id2] = tree.root_mut().push_children([0, 0]);
2502+
/// tree.node_mut(&id1).push_children([1, 3]);
2503+
/// tree.node_mut(&id2).push_children([7, 2, 4]);
2504+
/// // 0
2505+
/// // ╱ ╲
2506+
/// // ╱ ╲
2507+
/// // 0 0
2508+
/// // ╱ ╲ ╱|╲
2509+
/// // 1 3 7 2 4
2510+
///
2511+
/// tree.root_mut()
2512+
/// .recursive_set(
2513+
/// |current_value, children_values| match children_values.is_empty() {
2514+
/// true => *current_value, // is a leaf
2515+
/// false => children_values.iter().copied().sum(),
2516+
/// },
2517+
/// );
2518+
/// // 17
2519+
/// // ╱ ╲
2520+
/// // ╱ ╲
2521+
/// // 4 13
2522+
/// // ╱ ╲ ╱|╲
2523+
/// // 1 3 7 2 4
2524+
///
2525+
/// let bfs: Vec<_> = tree.root().walk::<Bfs>().copied().collect();
2526+
/// assert_eq!(bfs, [17, 4, 13, 1, 3, 7, 2, 4]);
2527+
/// ```
2528+
///
2529+
/// The following is a similar example where leaf nodes represent deterministic outcomes of
2530+
/// a process.
2531+
/// The root represents the current state.
2532+
/// The remaining nodes represent intermediate states that we can reach from its parent with
2533+
/// the given `probability`.
2534+
/// Our task is to compute `expected_value` of each state.
2535+
///
2536+
/// Since we know the value of the leaves with certainty, we set them while constructing the
2537+
/// tree. Then, we call `recursive_set` to compute the expected value of every other node.
2538+
///
2539+
/// ```
2540+
/// use orx_tree::*;
2541+
///
2542+
/// #[derive(Clone)]
2543+
/// struct State {
2544+
/// /// Probability of reaching this state from its parent.
2545+
/// probability: f64,
2546+
/// /// Expected value of the state; i.e., average of values of all leaves weighted by
2547+
/// /// the probability of being reached from this state.
2548+
/// expected_value: f64,
2549+
/// }
2550+
///
2551+
/// fn state(probability: f64, expected_value: f64) -> State {
2552+
/// State {
2553+
/// probability,
2554+
/// expected_value,
2555+
/// }
2556+
/// }
2557+
///
2558+
/// // (1.0, ???)
2559+
/// // ╱ ╲
2560+
/// // ╱ ╲
2561+
/// // ╱ ╲
2562+
/// // ╱ ╲
2563+
/// // (.3, ???) (.7, ???)
2564+
/// // ╱ ╲ | ╲
2565+
/// // ╱ ╲ | ╲
2566+
/// // (.2, 9) (.8, 2) (.9, 5) (.1, 4)
2567+
///
2568+
/// let mut tree = DynTree::<_>::new(state(1.0, 0.0));
2569+
///
2570+
/// let [id1, id2] = tree
2571+
/// .root_mut()
2572+
/// .push_children([state(0.3, 0.0), state(0.7, 0.0)]);
2573+
/// tree.node_mut(&id1)
2574+
/// .push_children([state(0.2, 9.0), state(0.8, 2.0)]);
2575+
/// tree.node_mut(&id2)
2576+
/// .push_children([state(0.9, 5.0), state(0.1, 4.0)]);
2577+
///
2578+
/// tree.root_mut()
2579+
/// .recursive_set(
2580+
/// |current_value, children_values| match children_values.is_empty() {
2581+
/// true => current_value.clone(), // is a leaf, we know expected value
2582+
/// false => {
2583+
/// let expected_value = children_values
2584+
/// .iter()
2585+
/// .fold(0.0, |a, x| a + x.probability * x.expected_value);
2586+
/// state(current_value.probability, expected_value)
2587+
/// }
2588+
/// },
2589+
/// );
2590+
/// // (1.0, 4.45)
2591+
/// // ╱ ╲
2592+
/// // ╱ ╲
2593+
/// // ╱ ╲
2594+
/// // ╱ ╲
2595+
/// // (.3, 3.4) (.7, 4.9)
2596+
/// // ╱ ╲ | ╲
2597+
/// // ╱ ╲ | ╲
2598+
/// // (.2, 9) (.8, 2) (.9, 5) (.1, 4)
2599+
///
2600+
/// let equals = |a: f64, b: f64| (a - b).abs() < 1e-5;
2601+
///
2602+
/// assert!(equals(tree.root().data().expected_value, 4.45));
2603+
/// assert!(equals(tree.node(&id1).data().expected_value, 3.40));
2604+
/// assert!(equals(tree.node(&id2).data().expected_value, 4.90));
2605+
/// ```
2606+
#[allow(clippy::missing_panics_doc)]
2607+
pub fn recursive_set(&mut self, compute_data: impl Fn(&V::Item, &[&V::Item]) -> V::Item) {
2608+
let iter = PostOrder::<OverPtr>::iter_ptr_with_owned_storage(self.node_ptr.clone());
2609+
let mut children_data = Vec::<&V::Item>::new();
2610+
2611+
for ptr in iter {
2612+
let node = unsafe { &mut *ptr.ptr_mut() };
2613+
let node_data = node.data().expect("is not closed");
2614+
2615+
for child_ptr in node.next().children_ptr() {
2616+
let data = unsafe { &*child_ptr.ptr() }.data().expect("is not closed");
2617+
children_data.push(data);
2618+
}
2619+
2620+
let new_data = compute_data(node_data, &children_data);
2621+
2622+
*node.data_mut().expect("is not closed") = new_data;
2623+
2624+
children_data.clear();
2625+
}
2626+
}
2627+
24582628
// subtree
24592629

24602630
/// Creates a subtree view including this node as the root and all of its descendants with their orientation relative

‎src/traversal/post_order/traverser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use core::marker::PhantomData;
1313
/// # Construction
1414
///
1515
/// A post order traverser can be created,
16+
///
1617
/// * either by using Default trait and providing its two generic type parameters
1718
/// * `PostOrder::<_, OverData>::default()` or `PostOrder::<_, OverDepthSiblingIdxData>::default()`, or
1819
/// * `PostOrder::<Dyn<u64>, OverData>::default()` or `PostOrder::<Dary<2, String>, OverDepthSiblingIdxData>::default()`

0 commit comments

Comments
 (0)
Please sign in to comment.