`TimeSlice`, `EventKind`, `KeyTable`, `CompetitorStore`, `Competitor` and the `storage` module were all public and none was obtainable from a `History` — `time_slices`, `agents` and `keys` are all private or `pub(crate)`. `TimeSlice` was the worst: `new`, `add_events`, `iteration`, `get_composition` and `get_results` were `pub` on a type you could only build standalone and never feed back into anything. Their sole consumer outside `src/` was `benches/batch.rs`, so a benchmark was dictating six public types. It is rewritten against the public API: a single-slice history's `converge` calls exactly the same per-slice sweep, so capping at one iteration measures the same code path. `N01` had zero references in the entire repository, including inside the crate; removed. `N00` and `N_INF` are EP identities (`Add` and `Mul`) and are now `pub(crate)` — a user reaching for `N_INF` as "an unknown competitor's prior" would get an improper distribution whose `mu()` silently reports 0.0. Adds the accessors their absence forced people around, from #70: `competitors()`, `competitor_count()` and `event_count()` (`size` had no accessor at all). Answering "who is best" previously meant materialising every competitor's full smoothed curve to read the last point of each. `KeyTable::keys` now iterates the dense reverse table rather than the forward `HashMap`, so `competitors()` yields insertion order rather than per-process hash order — the same hazard as #62, caught before it could reach a caller building a standings table. Two `CompetitorStore` methods (`is_empty`, `iter_mut`) had no callers anywhere and are gone; four more are now `#[cfg(test)]`, which is what they always were in practice. Worth recording a mistake: I first deleted `get_composition`/`get_results` on the strength of a "never used" warning, and the build broke — the warning came from the plain-lib target, where `#[cfg(test)]` callers in history.rs are not compiled. A dead-code warning from one target is not evidence about the others. BREAKING CHANGE: `TimeSlice`, `EventKind`, `KeyTable`, `CompetitorStore`, `Competitor`, the `storage` module, `N01`, `N00` and `N_INF` are no longer public. Refs #73, #70 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
126 lines
3.8 KiB
Rust
126 lines
3.8 KiB
Rust
use crate::{Index, competitor::Competitor, drift::Drift, time::Time};
|
|
|
|
/// Dense Vec-backed store for competitor state in History.
|
|
///
|
|
/// Indexed directly by Index.0, eliminating `HashMap` hashing in the
|
|
/// forward/backward sweep. Uses `Vec<Option<Competitor<T, D>>>` so slots can be
|
|
/// absent without an explicit present mask.
|
|
#[derive(Debug)]
|
|
pub struct CompetitorStore<T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
|
competitors: Vec<Option<Competitor<T, D>>>,
|
|
n_present: usize,
|
|
}
|
|
|
|
impl<T: Time, D: Drift<T>> Default for CompetitorStore<T, D> {
|
|
fn default() -> Self {
|
|
Self {
|
|
competitors: Vec::new(),
|
|
n_present: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Time, D: Drift<T>> CompetitorStore<T, D> {
|
|
#[must_use]
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
fn ensure_capacity(&mut self, idx: usize) {
|
|
if idx >= self.competitors.len() {
|
|
self.competitors.resize_with(idx + 1, || None);
|
|
}
|
|
}
|
|
|
|
pub fn insert(&mut self, idx: Index, competitor: Competitor<T, D>) {
|
|
self.ensure_capacity(idx.0);
|
|
if self.competitors[idx.0].is_none() {
|
|
self.n_present += 1;
|
|
}
|
|
self.competitors[idx.0] = Some(competitor);
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn get(&self, idx: Index) -> Option<&Competitor<T, D>> {
|
|
self.competitors.get(idx.0).and_then(|slot| slot.as_ref())
|
|
}
|
|
|
|
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<T, D>> {
|
|
self.competitors
|
|
.get_mut(idx.0)
|
|
.and_then(|slot| slot.as_mut())
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn contains(&self, idx: Index) -> bool {
|
|
self.get(idx).is_some()
|
|
}
|
|
|
|
/// Test-only: no code path in the crate needs a count.
|
|
#[cfg(test)]
|
|
#[must_use]
|
|
pub fn len(&self) -> usize {
|
|
self.n_present
|
|
}
|
|
|
|
/// Test-only: iterating every competitor is an assertion helper, not part
|
|
/// of inference, which walks slices rather than the store.
|
|
#[cfg(test)]
|
|
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<T, D>)> {
|
|
self.competitors
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
|
}
|
|
|
|
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<T, D>> {
|
|
self.competitors.iter_mut().filter_map(|s| s.as_mut())
|
|
}
|
|
}
|
|
|
|
impl<T: Time, D: Drift<T>> std::ops::Index<Index> for CompetitorStore<T, D> {
|
|
type Output = Competitor<T, D>;
|
|
fn index(&self, idx: Index) -> &Competitor<T, D> {
|
|
self.get(idx).expect("competitor not found at index")
|
|
}
|
|
}
|
|
|
|
impl<T: Time, D: Drift<T>> std::ops::IndexMut<Index> for CompetitorStore<T, D> {
|
|
fn index_mut(&mut self, idx: Index) -> &mut Competitor<T, D> {
|
|
self.get_mut(idx).expect("competitor not found at index")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{competitor::Competitor, drift::ConstantDrift};
|
|
|
|
#[test]
|
|
fn insert_then_get() {
|
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
|
let idx = Index(7);
|
|
store.insert(idx, Competitor::default());
|
|
assert!(store.contains(idx));
|
|
assert_eq!(store.len(), 1);
|
|
assert!(store.get(idx).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn iter_in_index_order() {
|
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
|
store.insert(Index(2), Competitor::default());
|
|
store.insert(Index(0), Competitor::default());
|
|
store.insert(Index(5), Competitor::default());
|
|
let keys: Vec<Index> = store.iter().map(|(i, _)| i).collect();
|
|
assert_eq!(keys, vec![Index(0), Index(2), Index(5)]);
|
|
}
|
|
|
|
#[test]
|
|
fn index_operator_works() {
|
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
|
store.insert(Index(3), Competitor::default());
|
|
let _ = &store[Index(3)];
|
|
}
|
|
}
|