use std::collections::HashMap;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct FrecencyEntry {
pub frequency: u64,
pub last_used: Instant,
}
pub struct FrecencyTracker {
entries: HashMap<String, FrecencyEntry>,
}
impl FrecencyTracker {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn record(&mut self, key: &str) {
let now = Instant::now();
self.entries
.entry(key.to_string())
.and_modify(|e| {
e.frequency += 1;
e.last_used = now;
})
.or_insert(FrecencyEntry {
frequency: 1,
last_used: now,
});
}
pub fn score(&self, key: &str) -> f64 {
match self.entries.get(key) {
Some(entry) => {
let hours = entry.last_used.elapsed().as_secs_f64() / 3600.0;
entry.frequency as f64 * (1.0 / (1.0 + hours))
}
None => 0.0,
}
}
pub fn top_n(&self, n: usize) -> Vec<(&str, f64)> {
let mut scored: Vec<(&str, f64)> = self
.entries
.keys()
.map(|k| (k.as_str(), self.score(k)))
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
scored.truncate(n);
scored
}
pub fn get(&self, key: &str) -> Option<&FrecencyEntry> {
self.entries.get(key)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
impl Default for FrecencyTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "frecency_tests.rs"]
mod tests;