use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
#[derive(Default)]
struct Family {
pages: u64,
seen: HashMap<u64, u64>,
}
pub struct TemplateModel {
shards: Box<[Mutex<HashMap<u64, Family>>]>,
}
impl Default for TemplateModel {
fn default() -> Self {
let n = std::thread::available_parallelism()
.map_or(1, |n| n.get())
.next_power_of_two();
Self {
shards: (0..n).map(|_| Mutex::new(HashMap::new())).collect(),
}
}
}
impl TemplateModel {
fn with<R>(&self, family: u64, f: impl FnOnce(&mut Family) -> R) -> R {
let shard = &self.shards[(family as usize) & (self.shards.len() - 1)];
f(shard
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(family)
.or_default())
}
pub fn observe(&self, family: u64, fingerprints: &[u64]) {
let unique: HashSet<u64> = fingerprints.iter().copied().collect();
self.with(family, |f| {
f.pages += 1;
for fp in unique {
*f.seen.entry(fp).or_default() += 1;
}
});
}
pub fn probs_observed(&self, family: u64, fingerprints: &[u64]) -> Vec<Option<f64>> {
self.with(family, |f| {
let n = f.pages.saturating_sub(1);
fingerprints
.iter()
.map(|fp| {
let k = f.seen.get(fp).copied().unwrap_or(0).saturating_sub(1);
(n > 0).then(|| (k as f64 + 1.0) / (n as f64 + 2.0))
})
.collect()
})
}
pub fn probs(&self, family: u64, fingerprints: &[u64]) -> Vec<Option<f64>> {
self.with(family, |f| {
let n = f.pages;
fingerprints
.iter()
.map(|fp| {
let k = f.seen.get(fp).copied().unwrap_or(0);
(n > 0).then(|| (k as f64 + 1.0) / (n as f64 + 2.0))
})
.collect()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn repeated_blocks_become_template_and_own_content_does_not() {
let m = TemplateModel::default();
for i in 0..20u64 {
m.observe(7, &[1, 2, 100 + i]);
}
let p = m.probs_observed(7, &[1, 2, 105]);
assert!(p[0].unwrap() > 0.9 && p[1].unwrap() > 0.9);
assert!(p[2].unwrap() < 0.1);
let q = m.probs(7, &[1, 999]);
assert!(q[0].unwrap() > 0.9 && q[1].unwrap() < 0.1);
assert_eq!(m.probs(8, &[1]), vec![None]);
m.observe(9, &[1]);
assert_eq!(m.probs_observed(9, &[1]), vec![None]);
}
}