use crate::id::NodeId;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexLookup {
Missing,
Ambiguous(Vec<NodeId>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Index<K: Ord + Clone> {
forward: BTreeMap<K, Vec<NodeId>>,
reverse: BTreeMap<NodeId, Vec<K>>,
}
impl<K: Ord + Clone> Default for Index<K> {
fn default() -> Self {
Self::new()
}
}
impl<K: Ord + Clone> Index<K> {
#[must_use]
pub const fn new() -> Self {
Self {
forward: BTreeMap::new(),
reverse: BTreeMap::new(),
}
}
pub fn insert(&mut self, key: K, node: NodeId) {
let nodes = self.forward.entry(key.clone()).or_default();
if !nodes.contains(&node) {
nodes.push(node);
}
let keys = self.reverse.entry(node).or_default();
if !keys.contains(&key) {
keys.push(key);
}
}
pub fn remove(&mut self, key: &K, node: NodeId) -> bool {
let Some(nodes) = self.forward.get_mut(key) else {
return false;
};
let before = nodes.len();
nodes.retain(|candidate| *candidate != node);
let removed = nodes.len() != before;
if nodes.is_empty() {
let _ = self.forward.remove(key);
}
if let Some(keys) = self.reverse.get_mut(&node) {
keys.retain(|candidate| candidate != key);
if keys.is_empty() {
let _ = self.reverse.remove(&node);
}
}
removed
}
pub fn remove_node(&mut self, node: NodeId) {
let Some(keys) = self.reverse.remove(&node) else {
return;
};
for key in keys {
if let Some(nodes) = self.forward.get_mut(&key) {
nodes.retain(|candidate| *candidate != node);
if nodes.is_empty() {
let _ = self.forward.remove(&key);
}
}
}
}
pub fn remove_key(&mut self, key: &K) {
let Some(nodes) = self.forward.remove(key) else {
return;
};
for node in nodes {
if let Some(keys) = self.reverse.get_mut(&node) {
keys.retain(|candidate| candidate != key);
if keys.is_empty() {
let _ = self.reverse.remove(&node);
}
}
}
}
#[must_use]
pub fn get(&self, key: &K) -> &[NodeId] {
self.forward.get(key).map_or(&[], Vec::as_slice)
}
pub fn unique(&self, key: &K) -> Result<NodeId, IndexLookup> {
match self.get(key) {
[] => Err(IndexLookup::Missing),
[single] => Ok(*single),
many => Err(IndexLookup::Ambiguous(many.to_vec())),
}
}
#[must_use]
pub fn keys_of(&self, node: NodeId) -> &[K] {
self.reverse.get(&node).map_or(&[], Vec::as_slice)
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.forward.keys()
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &[NodeId])> {
self.forward.iter().map(|(k, v)| (k, v.as_slice()))
}
#[must_use]
pub fn len(&self) -> usize {
self.forward.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.forward.is_empty()
}
pub fn clear(&mut self) {
self.forward.clear();
self.reverse.clear();
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
use crate::id::IdSpace;
use alloc::string::{String, ToString};
use alloc::vec;
fn n(counter: u64) -> NodeId {
NodeId::from_parts(IdSpace::MODEL, counter)
}
#[test]
fn a_unique_key_resolves_to_its_node() {
let mut index: Index<String> = Index::new();
index.insert("::Robot::Pose".to_string(), n(1));
assert_eq!(index.unique(&"::Robot::Pose".to_string()), Ok(n(1)));
}
#[test]
fn a_missing_key_reports_missing_not_an_empty_success() {
let index: Index<String> = Index::new();
assert_eq!(
index.unique(&"::Nope".to_string()),
Err(IndexLookup::Missing)
);
}
#[test]
fn a_duplicated_key_reports_every_candidate() {
let mut index: Index<String> = Index::new();
index.insert("::Dup".to_string(), n(1));
index.insert("::Dup".to_string(), n(2));
assert_eq!(
index.unique(&"::Dup".to_string()),
Err(IndexLookup::Ambiguous(vec![n(1), n(2)]))
);
}
#[test]
fn removing_the_last_node_for_a_key_drops_the_key() {
let mut index: Index<String> = Index::new();
index.insert("::Gone".to_string(), n(1));
assert!(index.remove(&"::Gone".to_string(), n(1)));
assert_eq!(index.len(), 0);
assert!(index.get(&"::Gone".to_string()).is_empty());
assert!(!index.remove(&"::Gone".to_string(), n(1)));
}
#[test]
fn remove_node_purges_every_key_the_node_appeared_under() {
let mut index: Index<String> = Index::new();
index.insert("a".to_string(), n(1));
index.insert("b".to_string(), n(1));
index.insert("b".to_string(), n(2));
index.remove_node(n(1));
assert!(index.get(&"a".to_string()).is_empty());
assert_eq!(index.get(&"b".to_string()), &[n(2)]);
assert!(index.keys_of(n(1)).is_empty());
}
#[test]
fn candidates_stay_in_insertion_order_for_deterministic_diagnostics() {
let mut index: Index<String> = Index::new();
index.insert("k".to_string(), n(3));
index.insert("k".to_string(), n(1));
index.insert("k".to_string(), n(2));
assert_eq!(index.get(&"k".to_string()), &[n(3), n(1), n(2)]);
}
#[test]
fn inserting_the_same_pair_twice_does_not_duplicate_it() {
let mut index: Index<String> = Index::new();
index.insert("k".to_string(), n(1));
index.insert("k".to_string(), n(1));
assert_eq!(index.get(&"k".to_string()), &[n(1)]);
assert_eq!(index.keys_of(n(1)).len(), 1);
}
}