use std::{ borrow::{Borrow, ToOwned}, collections::HashMap, hash::Hash, }; use crate::Index; /// Maps user keys to internal `Index` handles. /// /// Renamed from the former `IndexMap` to avoid colliding with the `indexmap` /// crate. Power users can promote `&K` to `Index` via `get_or_create` and /// skip the lookup on subsequent hot-path calls. #[derive(Debug)] pub struct KeyTable { forward: HashMap, /// Reverse mapping, indexed by `Index.0`. /// /// Indices are handed out densely and sequentially, so position *is* the /// index and `key()` is a lookup rather than a scan over every entry. reverse: Vec, } impl KeyTable where K: Eq + Hash + Clone, { #[must_use] pub(crate) fn new() -> Self { Self { forward: HashMap::new(), reverse: Vec::new(), } } pub(crate) fn get(&self, k: &Q) -> Option where K: Borrow, { self.forward.get(k).cloned() } pub(crate) fn get_or_create>( &mut self, k: &Q, ) -> Index where K: Borrow, { if let Some(idx) = self.forward.get(k) { *idx } else { let idx = Index::from(self.reverse.len()); let owned = k.to_owned(); self.reverse.push(owned.clone()); self.forward.insert(owned, idx); idx } } #[must_use] pub(crate) fn key(&self, idx: Index) -> Option<&K> { self.reverse.get(idx.0) } /// Every key, in the order they were first interned. /// /// Iterates the dense reverse table rather than the forward `HashMap`. /// Rust seeds its default hasher per process, so a `HashMap` walk yields a /// different order on every run — which is fine for membership but not for /// anything a caller might sum, sort or print. pub(crate) fn keys(&self) -> impl ExactSizeIterator { self.reverse.iter() } #[must_use] pub(crate) fn len(&self) -> usize { self.reverse.len() } } impl Default for KeyTable where K: Eq + Hash + Clone, { fn default() -> Self { KeyTable::new() } }