|
1 | | -// SPDX-FileCopyrightText: 2024 TriliTech <contact@trili.tech> |
2 | | -// |
| 1 | +// SPDX-FileCopyrightText: 2025 TriliTech <contact@trili.tech> |
3 | 2 | // SPDX-License-Identifier: MIT |
4 | 3 |
|
5 | | -//! Module for tree utils like types, traversals. |
6 | | -//! |
7 | | -//! All the traversals implemented in this module should be the same to maintain consistency, |
8 | | -//! which is required for serialisation / deserialisation |
9 | | -
|
10 | | -/// Intermediary either-like type for implementing [`impl_modify_map_collect`] |
11 | | -#[derive(Clone)] |
12 | | -pub enum ModifyResult<D, N, L> { |
13 | | - /// Current subtree should be replaced with a node containing the given children, |
14 | | - /// and an auxiliary data for extra context if needed. |
15 | | - /// Traversal should continue recursively. |
16 | | - NodeContinue(D, Vec<N>), |
17 | | - /// Current subtree is replaced by a leaf containing the given data. |
18 | | - LeafStop(L), |
19 | | -} |
20 | | - |
21 | | -/// Perform generic modify_map_collect |
22 | | -/// |
23 | | -/// This is done in 3 steps while traversing the tree in a pre-order DFS traversal: |
24 | | -/// 1. Apply `modify` on current subtree: This operation changes the structure of the current |
25 | | -/// subtree before traversing its children. |
26 | | -/// 2. When encountering leaves, `map` is called to transform a leaf from `A` to `B` type. |
27 | | -/// This is done on children of subtrees which have been traversed after `modify` was called. |
28 | | -/// 3. After modifying & mapping the children of a node, the `collect` method gathers the newly |
29 | | -/// modified & mapped subtrees to create the new subtree. |
30 | | -pub fn impl_modify_map_collect< |
31 | | - InterimLeafData, // InterimLeafData -> FinalLeafData when applying `map` |
32 | | - FinalLeafData, // [FinalLeafData] -> FinalLeafData when applying `collect` |
33 | | - AuxTreeData, // Type of auxiliary data held for a subtree |
34 | | - InputTree, |
35 | | - OutputTree: From<FinalLeafData>, |
36 | | - TreeModifier: FnMut(InputTree) -> ModifyResult<AuxTreeData, InputTree, InterimLeafData>, |
37 | | - LeafMapper: FnMut(InterimLeafData) -> FinalLeafData, |
38 | | - Collector: FnMut(AuxTreeData, Vec<OutputTree>) -> OutputTree, |
39 | | ->( |
40 | | - root: InputTree, |
41 | | - mut modify: TreeModifier, |
42 | | - mut map: LeafMapper, |
43 | | - mut collect: Collector, |
44 | | -) -> OutputTree { |
45 | | - enum ProcessEvents<ProcessEvent, CollectAuxTreeData> { |
46 | | - Node(ProcessEvent), |
47 | | - Collect(CollectAuxTreeData, usize), |
48 | | - } |
49 | | - |
50 | | - let mut process = vec![ProcessEvents::Node(root)]; |
51 | | - let mut done: Vec<OutputTree> = vec![]; |
52 | | - |
53 | | - while let Some(event) = process.pop() { |
54 | | - match event { |
55 | | - ProcessEvents::Node(subtree) => match modify(subtree) { |
56 | | - ModifyResult::LeafStop(data) => { |
57 | | - // Instead of pushing a single leaf process on the Process-queue, |
58 | | - // map the data and append it directly to the Done-queue |
59 | | - done.push(OutputTree::from(map(data))); |
60 | | - } |
61 | | - ModifyResult::NodeContinue(node_data, children) => { |
62 | | - // the only case where we push further process events in the process queue |
63 | | - // We have to first push a collect event to know how many children should be collected when forming back the current subtree |
64 | | - process.push(ProcessEvents::Collect(node_data, children.len())); |
65 | | - |
66 | | - process.extend( |
67 | | - children |
68 | | - .into_iter() |
69 | | - .rev() |
70 | | - .map(|child| ProcessEvents::Node(child)), |
71 | | - ); |
72 | | - } |
73 | | - }, |
74 | | - ProcessEvents::Collect(node_data, count) => { |
75 | | - // We need to reconstruct a subtree which is made of `count` children |
76 | | - // No panic: We are guaranteed count < done.len() since every Collect(size) |
77 | | - // corresponds to size nodes pushed to Done-queue |
78 | | - let children = done.split_off(done.len() - count); |
79 | | - done.push(collect(node_data, children)); |
80 | | - } |
81 | | - } |
82 | | - } |
83 | | - |
84 | | - // No Panic: We only add a single node as root at the beginning of the algorithm |
85 | | - // which corresponds to this last node in the Done-queue |
86 | | - done.pop() |
87 | | - .filter(|_| done.is_empty()) |
88 | | - .expect("Unexpected number of results") |
89 | | -} |
0 commit comments