use std::collections::{BTreeSet, HashMap};
use crate::sdf::Path;
#[derive(Debug, Clone)]
pub struct PathTable<V> {
nodes: HashMap<Path, Node<V>>,
len: usize,
}
#[derive(Debug, Clone)]
struct Node<V> {
value: Option<V>,
children: BTreeSet<Path>,
}
impl<V> Default for PathTable<V> {
fn default() -> Self {
Self {
nodes: HashMap::new(),
len: 0,
}
}
}
impl<V> PathTable<V> {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn clear(&mut self) {
self.nodes.clear();
self.len = 0;
}
pub fn contains_key(&self, path: &Path) -> bool {
self.nodes.get(path).is_some_and(|n| n.value.is_some())
}
pub fn get(&self, path: &Path) -> Option<&V> {
self.nodes.get(path).and_then(|n| n.value.as_ref())
}
pub fn get_mut(&mut self, path: &Path) -> Option<&mut V> {
self.nodes.get_mut(path).and_then(|n| n.value.as_mut())
}
pub fn insert(&mut self, path: Path, value: V) -> Option<V> {
if let Some(node) = self.nodes.get_mut(&path) {
let prev = node.value.replace(value);
if prev.is_none() {
self.len += 1;
}
return prev;
}
self.nodes.insert(
path.clone(),
Node {
value: Some(value),
children: BTreeSet::new(),
},
);
self.len += 1;
self.link_ancestors(path);
None
}
pub fn get_or_insert_default(&mut self, path: &Path) -> &mut V
where
V: Default,
{
if !self.contains_key(path) {
self.insert(path.clone(), V::default());
}
self.get_mut(path).expect("just inserted")
}
pub fn remove(&mut self, path: &Path) -> Option<V> {
let node = self.nodes.get_mut(path)?;
let value = node.value.take()?;
self.len -= 1;
if node.children.is_empty() {
self.nodes.remove(path);
self.prune_ancestors(path);
}
Some(value)
}
pub fn iter(&self) -> impl Iterator<Item = (&Path, &V)> {
self.nodes.iter().filter_map(|(p, n)| n.value.as_ref().map(|v| (p, v)))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Path, &mut V)> {
self.nodes
.iter_mut()
.filter_map(|(p, n)| n.value.as_mut().map(|v| (p, v)))
}
pub fn ancestors<'a>(&'a self, path: &Path) -> impl Iterator<Item = (&'a Path, &'a V)> {
path.ancestors().filter_map(move |p| {
let (key, node) = self.nodes.get_key_value(&p)?;
node.value.as_ref().map(|v| (key, v))
})
}
pub fn nearest_ancestor(&self, path: &Path) -> Option<(&Path, &V)> {
self.ancestors(path).next()
}
pub fn contains_ancestor(&self, path: &Path) -> bool {
self.nearest_ancestor(path).is_some()
}
pub fn subtree<'a>(&'a self, prefix: &Path) -> impl Iterator<Item = (&'a Path, &'a V)> {
let mut out: Vec<(&'a Path, &'a V)> = Vec::new();
self.collect_subtree(prefix, &mut out);
out.into_iter()
}
pub fn remove_subtree(&mut self, prefix: &Path) -> Vec<(Path, V)> {
if !self.nodes.contains_key(prefix) {
return Vec::new();
}
let mut removed = Vec::new();
self.remove_subtree_into(prefix, &mut removed);
self.prune_ancestors(prefix);
removed
}
fn remove_subtree_into(&mut self, root: &Path, out: &mut Vec<(Path, V)>) {
let Some((path, node)) = self.nodes.remove_entry(root) else {
return;
};
for child in &node.children {
self.remove_subtree_into(child, out);
}
if let Some(value) = node.value {
self.len -= 1;
out.push((path, value));
}
}
fn link_ancestors(&mut self, path: Path) {
let mut child = path;
while let Some(parent) = child.parent() {
match self.nodes.get_mut(&parent) {
Some(node) => {
node.children.insert(child);
return;
}
None => {
let mut children = BTreeSet::new();
children.insert(child);
self.nodes.insert(parent.clone(), Node { value: None, children });
child = parent;
}
}
}
}
fn prune_ancestors(&mut self, path: &Path) {
let mut child = path.clone();
while let Some(parent) = child.parent() {
let Some(node) = self.nodes.get_mut(&parent) else {
return;
};
node.children.remove(&child);
if node.value.is_some() || !node.children.is_empty() {
return;
}
self.nodes.remove(&parent);
child = parent;
}
}
fn collect_subtree<'a>(&'a self, root: &Path, out: &mut Vec<(&'a Path, &'a V)>) {
let Some((path, node)) = self.nodes.get_key_value(root) else {
return;
};
if let Some(value) = &node.value {
out.push((path, value));
}
for child in &node.children {
self.collect_subtree(child, out);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf::path;
fn p(s: &str) -> Path {
path(s).expect("valid path")
}
#[test]
fn insert_get_remove() {
let mut t: PathTable<i32> = PathTable::new();
assert!(t.is_empty());
assert_eq!(t.insert(p("/A/B"), 1), None);
assert_eq!(t.insert(p("/A/B"), 2), Some(1));
assert_eq!(t.get(&p("/A/B")), Some(&2));
assert!(t.contains_key(&p("/A/B")));
assert_eq!(t.len(), 1);
assert_eq!(t.remove(&p("/A/B")), Some(2));
assert_eq!(t.remove(&p("/A/B")), None);
assert!(t.is_empty());
}
#[test]
fn get_mut_edits_in_place() {
let mut t: PathTable<Vec<i32>> = PathTable::new();
t.get_or_insert_default(&p("/A")).push(7);
t.get_or_insert_default(&p("/A")).push(8);
assert_eq!(t.get(&p("/A")), Some(&vec![7, 8]));
assert_eq!(t.len(), 1);
}
#[test]
fn intermediate_nodes_uncounted() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A/B/C"), 1);
assert_eq!(t.len(), 1);
assert_eq!(t.get(&p("/A")), None);
assert_eq!(t.get(&p("/A/B")), None);
assert_eq!(t.iter().count(), 1);
}
#[test]
fn remove_prunes_ancestors() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A/B/C"), 1);
t.remove(&p("/A/B/C"));
assert!(t.nodes.is_empty(), "intermediate ancestors should be pruned");
}
#[test]
fn remove_keeps_node_with_children() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A"), 1);
t.insert(p("/A/B"), 2);
assert_eq!(t.remove(&p("/A")), Some(1));
assert_eq!(t.get(&p("/A/B")), Some(&2));
assert_eq!(t.len(), 1);
}
#[test]
fn subtree_excludes_sibling() {
let mut t: PathTable<i32> = PathTable::new();
for (path, n) in [("/Foo", 0), ("/Foo.attr", 1), ("/Foo/Bar", 2), ("/Foobar", 3)] {
t.insert(p(path), n);
}
t.insert(p("/Foo{v=s}"), 4);
let mut got: Vec<&str> = t.subtree(&p("/Foo")).map(|(path, _)| path.as_str()).collect();
got.sort();
assert_eq!(got, vec!["/Foo", "/Foo.attr", "/Foo/Bar", "/Foo{v=s}"]);
}
#[test]
fn subtree_from_intermediate_prefix() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A/B/C"), 1);
t.insert(p("/A/B/D"), 2);
let mut got: Vec<&str> = t.subtree(&p("/A/B")).map(|(path, _)| path.as_str()).collect();
got.sort();
assert_eq!(got, vec!["/A/B/C", "/A/B/D"]);
}
#[test]
fn ancestors_skips_intermediate() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A"), 1);
t.insert(p("/A/B/C"), 3);
let got: Vec<(&str, i32)> = t.ancestors(&p("/A/B/C")).map(|(path, v)| (path.as_str(), *v)).collect();
assert_eq!(got, vec![("/A/B/C", 3), ("/A", 1)]);
assert_eq!(t.nearest_ancestor(&p("/A/B/C")), Some((&p("/A/B/C"), &3)));
assert_eq!(t.nearest_ancestor(&p("/A/B")), Some((&p("/A"), &1)));
assert!(t.contains_ancestor(&p("/A/B")));
assert!(!t.contains_ancestor(&p("/X/Y")));
}
#[test]
fn subtree_unknown_prefix_empty() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A/B"), 1);
assert_eq!(t.subtree(&p("/X")).count(), 0);
assert_eq!(t.subtree(&p("/A/B/C")).count(), 0);
}
#[test]
fn remove_subtree_prunes() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A/B"), 1);
t.insert(p("/A/B/C"), 2);
t.insert(p("/A/Sibling"), 3);
let subtree = t.remove_subtree(&p("/A/B"));
let mut removed: Vec<&str> = subtree.iter().map(|(p, _)| p.as_str()).collect();
removed.sort();
assert_eq!(removed, vec!["/A/B", "/A/B/C"]);
assert_eq!(t.get(&p("/A/Sibling")), Some(&3));
assert!(!t.contains_key(&p("/A/B")));
assert!(!t.contains_key(&p("/A/B/C")));
assert_eq!(t.len(), 1);
}
#[test]
fn remove_subtree_at_root() {
let mut t: PathTable<i32> = PathTable::new();
t.insert(p("/A"), 1);
t.insert(p("/B/C"), 2);
let removed = t.remove_subtree(&Path::abs_root());
assert_eq!(removed.len(), 2);
assert!(t.is_empty());
assert_eq!(t.nodes.len(), 0);
}
}