use std::collections::{BTreeSet, HashMap};
use std::hash::Hash;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use ahash::RandomState;
use atomic_traits::{Atomic, NumOps};
use num_traits::One;
pub struct TopKNgrams<T, A>
where
T: Ord + Clone + Sized,
A: Atomic + NumOps,
<A as Atomic>::Type: One + Ord + Clone + Copy,
{
k: usize,
topk: BTreeSet<(<A as Atomic>::Type, Rc<Vec<T>>)>,
ngrams: HashMap<Rc<Vec<T>>, <A as Atomic>::Type, RandomState>,
pub(crate) min_count: <A as Atomic>::Type,
min_count_atomic: Arc<A>,
}
impl<T, A> TopKNgrams<T, A>
where
T: Ord + Clone + Sized + Hash,
A: Atomic + NumOps,
<A as Atomic>::Type: One + Ord + Clone + Copy,
{
pub fn new(k: usize) -> Self {
Self {
k,
topk: BTreeSet::new(),
ngrams: HashMap::with_capacity_and_hasher(k + 1, RandomState::new()),
min_count: <A as Atomic>::Type::one(),
min_count_atomic: Arc::new(<A as Atomic>::new(<A as Atomic>::Type::one())),
}
}
pub fn min_count(&self) -> Arc<A> {
self.min_count_atomic.clone()
}
pub fn insert(&mut self, ngram: Vec<T>, count: <A as Atomic>::Type) {
if count >= self.min_count {
let ngram = Rc::new(ngram);
if let Some(old_count) = self.ngrams.get_mut(&ngram) {
if count <= *old_count {
return;
}
self.topk.remove(&(*old_count, ngram.clone()));
*old_count = count;
} else {
self.ngrams.insert(ngram.clone(), count);
}
self.topk.insert((count, ngram.clone()));
}
let mut update_min_count = false;
while self.topk.len() > self.k {
let (_, ngram) = self.topk.pop_first().unwrap();
self.ngrams.remove(&ngram);
update_min_count = true;
}
if update_min_count {
if let Some((new_min_count, _)) = self.topk.first() {
if *new_min_count != self.min_count {
self.min_count = *new_min_count;
self.min_count_atomic
.store(*new_min_count, Ordering::Relaxed);
}
}
}
}
pub fn drain(&mut self) -> Vec<(Rc<Vec<T>>, <A as Atomic>::Type)> {
let mut out: Vec<(Rc<Vec<T>>, <A as Atomic>::Type)> = Vec::with_capacity(self.k);
while let Some((count, ngram)) = self.topk.pop_last() {
self.ngrams.remove(&ngram);
out.push((ngram, count))
}
self.min_count = <A as Atomic>::Type::one();
self.min_count_atomic
.store(<A as Atomic>::Type::one(), Ordering::Relaxed);
out
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use std::sync::atomic::AtomicU32;
use super::TopKNgrams;
#[test]
fn test_adding_same_ngram_multiple_times() {
let mut topk: TopKNgrams<String, AtomicU32> = TopKNgrams::new(3);
let ngram1 = vec!["foo".into(), "bar".into()];
let ngram2 = vec!["bar".into(), "baz".into()];
let ngram3 = vec!["baz".into(), "foo".into()];
topk.insert(ngram1.clone(), 3);
topk.insert(ngram2, 2);
topk.insert(ngram3, 1);
topk.insert(ngram1.clone(), 3);
assert_eq!(topk.ngrams.len(), 3);
assert_eq!(topk.topk.len(), 3);
assert_eq!(topk.ngrams.get(&Rc::new(ngram1.clone())), Some(&3));
topk.insert(ngram1.clone(), 4);
assert_eq!(topk.ngrams.len(), 3);
assert_eq!(topk.topk.len(), 3);
assert_eq!(topk.ngrams.get(&Rc::new(ngram1.clone())), Some(&4));
topk.insert(ngram1.clone(), 2);
assert_eq!(topk.ngrams.len(), 3);
assert_eq!(topk.topk.len(), 3);
assert_eq!(topk.ngrams.get(&Rc::new(ngram1)), Some(&4));
}
}