Skip to main content

oxihuman_core/
tree_index.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Hierarchical tree index: parent-child relationships by u32 id.
6
7use std::collections::HashMap;
8
9/// A node in the tree.
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct TreeNode {
13    pub id: u32,
14    pub label: String,
15    pub parent: Option<u32>,
16    pub children: Vec<u32>,
17}
18
19/// Tree index structure.
20#[allow(dead_code)]
21#[derive(Debug, Clone, Default)]
22pub struct TreeIndex {
23    nodes: HashMap<u32, TreeNode>,
24    roots: Vec<u32>,
25    next_id: u32,
26}
27
28/// Create a new empty `TreeIndex`.
29#[allow(dead_code)]
30pub fn new_tree_index() -> TreeIndex {
31    TreeIndex::default()
32}
33
34/// Add a root node and return its id.
35#[allow(dead_code)]
36pub fn ti_add_root(ti: &mut TreeIndex, label: &str) -> u32 {
37    let id = ti.next_id;
38    ti.next_id += 1;
39    ti.nodes.insert(
40        id,
41        TreeNode {
42            id,
43            label: label.to_string(),
44            parent: None,
45            children: Vec::new(),
46        },
47    );
48    ti.roots.push(id);
49    id
50}
51
52/// Add a child under `parent_id` and return its id.
53#[allow(dead_code)]
54pub fn ti_add_child(ti: &mut TreeIndex, parent_id: u32, label: &str) -> Option<u32> {
55    if !ti.nodes.contains_key(&parent_id) {
56        return None;
57    }
58    let id = ti.next_id;
59    ti.next_id += 1;
60    ti.nodes.insert(
61        id,
62        TreeNode {
63            id,
64            label: label.to_string(),
65            parent: Some(parent_id),
66            children: Vec::new(),
67        },
68    );
69    if let Some(parent) = ti.nodes.get_mut(&parent_id) {
70        parent.children.push(id);
71    }
72    Some(id)
73}
74
75/// Get label for a node.
76#[allow(dead_code)]
77pub fn ti_label(ti: &TreeIndex, id: u32) -> Option<&str> {
78    ti.nodes.get(&id).map(|n| n.label.as_str())
79}
80
81/// Get parent id.
82#[allow(dead_code)]
83pub fn ti_parent(ti: &TreeIndex, id: u32) -> Option<u32> {
84    ti.nodes.get(&id).and_then(|n| n.parent)
85}
86
87/// Get children of a node.
88#[allow(dead_code)]
89pub fn ti_children(ti: &TreeIndex, id: u32) -> Vec<u32> {
90    ti.nodes
91        .get(&id)
92        .map(|n| n.children.clone())
93        .unwrap_or_default()
94}
95
96/// Total number of nodes.
97#[allow(dead_code)]
98pub fn ti_count(ti: &TreeIndex) -> usize {
99    ti.nodes.len()
100}
101
102/// Depth of a node from root (root = 0).
103#[allow(dead_code)]
104pub fn ti_depth(ti: &TreeIndex, id: u32) -> usize {
105    let mut depth = 0;
106    let mut cur = id;
107    while let Some(p) = ti.nodes.get(&cur).and_then(|n| n.parent) {
108        cur = p;
109        depth += 1;
110    }
111    depth
112}
113
114/// Collect all descendants (BFS).
115#[allow(dead_code)]
116pub fn ti_descendants(ti: &TreeIndex, id: u32) -> Vec<u32> {
117    let mut result = Vec::new();
118    let mut queue = vec![id];
119    while !queue.is_empty() {
120        let cur = queue.remove(0);
121        if let Some(node) = ti.nodes.get(&cur) {
122            for &child in &node.children {
123                result.push(child);
124                queue.push(child);
125            }
126        }
127    }
128    result
129}
130
131/// List root node ids.
132#[allow(dead_code)]
133pub fn ti_roots(ti: &TreeIndex) -> &[u32] {
134    &ti.roots
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_add_root() {
143        let mut ti = new_tree_index();
144        let id = ti_add_root(&mut ti, "root");
145        assert_eq!(ti_label(&ti, id), Some("root"));
146    }
147
148    #[test]
149    fn test_add_child() {
150        let mut ti = new_tree_index();
151        let root = ti_add_root(&mut ti, "root");
152        let child = ti_add_child(&mut ti, root, "child").expect("should succeed");
153        assert_eq!(ti_parent(&ti, child), Some(root));
154    }
155
156    #[test]
157    fn test_children() {
158        let mut ti = new_tree_index();
159        let r = ti_add_root(&mut ti, "r");
160        let c1 = ti_add_child(&mut ti, r, "c1").expect("should succeed");
161        let c2 = ti_add_child(&mut ti, r, "c2").expect("should succeed");
162        let children = ti_children(&ti, r);
163        assert!(children.contains(&c1) && children.contains(&c2));
164    }
165
166    #[test]
167    fn test_depth() {
168        let mut ti = new_tree_index();
169        let r = ti_add_root(&mut ti, "r");
170        let c = ti_add_child(&mut ti, r, "c").expect("should succeed");
171        let gc = ti_add_child(&mut ti, c, "gc").expect("should succeed");
172        assert_eq!(ti_depth(&ti, r), 0);
173        assert_eq!(ti_depth(&ti, c), 1);
174        assert_eq!(ti_depth(&ti, gc), 2);
175    }
176
177    #[test]
178    fn test_descendants() {
179        let mut ti = new_tree_index();
180        let r = ti_add_root(&mut ti, "r");
181        let c = ti_add_child(&mut ti, r, "c").expect("should succeed");
182        ti_add_child(&mut ti, c, "gc").expect("should succeed");
183        let desc = ti_descendants(&ti, r);
184        assert_eq!(desc.len(), 2);
185    }
186
187    #[test]
188    fn test_count() {
189        let mut ti = new_tree_index();
190        let r = ti_add_root(&mut ti, "r");
191        ti_add_child(&mut ti, r, "c").expect("should succeed");
192        assert_eq!(ti_count(&ti), 2);
193    }
194
195    #[test]
196    fn test_roots_list() {
197        let mut ti = new_tree_index();
198        let r1 = ti_add_root(&mut ti, "r1");
199        let r2 = ti_add_root(&mut ti, "r2");
200        let roots = ti_roots(&ti);
201        assert!(roots.contains(&r1) && roots.contains(&r2));
202    }
203
204    #[test]
205    fn test_invalid_parent() {
206        let mut ti = new_tree_index();
207        let result = ti_add_child(&mut ti, 999, "x");
208        assert!(result.is_none());
209    }
210
211    #[test]
212    fn test_no_parent_for_root() {
213        let mut ti = new_tree_index();
214        let r = ti_add_root(&mut ti, "r");
215        assert!(ti_parent(&ti, r).is_none());
216    }
217}