`h.event(1).team(["x"]).team(["y"]).ranking([0, 1]);` without the terminal `.commit()` was a silent no-op: no warning, no error, and the next thing the caller does is converge an empty history and read `None` skills. `EventBuilder` already carried a `#[must_use]`; the value types around it did not, so the same silence covered `Team::with_members`, `Member::new`, `Outcome::*`, `Joint` and `Prediction::outcomes`. `#[must_use]` now goes on the *types* rather than being sprinkled over methods, which covers every constructor and builder setter at once and gives the crate a rule where it previously had a list. Verified by compiling a program that drops each one and reading the warnings back, rather than by assuming the attribute took. Visibility, from #73: `Gaussian::damp_natural` was reachable from outside the crate despite being an EP damping internal called only from `src/factor/`. The stray `pub fn`s inside the private `time_slice`, `key_table` and `matrix` modules are now `pub(crate)`, so their visibility states what it means instead of relying on the module being private. `storage/mod.rs` and `factor/mod.rs` become `storage.rs` and `factor.rs`. Closes #67. Refs #73. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
90 lines
2.2 KiB
Rust
90 lines
2.2 KiB
Rust
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<K> {
|
|
forward: HashMap<K, Index>,
|
|
/// 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<K>,
|
|
}
|
|
|
|
impl<K> KeyTable<K>
|
|
where
|
|
K: Eq + Hash + Clone,
|
|
{
|
|
#[must_use]
|
|
pub(crate) fn new() -> Self {
|
|
Self {
|
|
forward: HashMap::new(),
|
|
reverse: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> Option<Index>
|
|
where
|
|
K: Borrow<Q>,
|
|
{
|
|
self.forward.get(k).cloned()
|
|
}
|
|
|
|
pub(crate) fn get_or_create<Q: ?Sized + Hash + Eq + ToOwned<Owned = K>>(
|
|
&mut self,
|
|
k: &Q,
|
|
) -> Index
|
|
where
|
|
K: Borrow<Q>,
|
|
{
|
|
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<Item = &K> {
|
|
self.reverse.iter()
|
|
}
|
|
|
|
#[must_use]
|
|
pub(crate) fn len(&self) -> usize {
|
|
self.reverse.len()
|
|
}
|
|
}
|
|
|
|
impl<K> Default for KeyTable<K>
|
|
where
|
|
K: Eq + Hash + Clone,
|
|
{
|
|
fn default() -> Self {
|
|
KeyTable::new()
|
|
}
|
|
}
|