use std::collections::HashMap;
use std::hash::Hash;
use hashlink::LinkedHashMap;
use crate::{Cache, CacheEntry};
pub struct LfuCache<K, V> {
cap: usize,
min_freq: u64,
key_to_entry: HashMap<K, (u64, CacheEntry<V>), ahash::RandomState>,
freq_to_keys: HashMap<u64, LinkedHashMap<K, (), ahash::RandomState>, ahash::RandomState>,
}
impl<K, V> LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
#[must_use]
pub fn new(cap: usize) -> Self {
LfuCache {
cap,
min_freq: 0,
key_to_entry: HashMap::with_hasher(ahash::RandomState::default()),
freq_to_keys: HashMap::with_hasher(ahash::RandomState::default()),
}
}
fn increment_freq(&mut self, key: &K) {
let (freq, _entry) = match self.key_to_entry.get_mut(key) {
Some(pair) => pair,
None => return,
};
let old_freq = *freq;
let new_freq = old_freq + 1;
*freq = new_freq;
if let Some(bucket) = self.freq_to_keys.get_mut(&old_freq) {
bucket.remove(key);
if bucket.is_empty() {
self.freq_to_keys.remove(&old_freq);
if old_freq == self.min_freq {
self.min_freq = new_freq;
}
}
}
self.freq_to_keys
.entry(new_freq)
.or_insert_with(|| LinkedHashMap::with_hasher(ahash::RandomState::default()))
.insert(key.clone(), ());
}
fn evict(&mut self) -> Option<V> {
let bucket = self.freq_to_keys.get_mut(&self.min_freq)?;
let (evict_key, ()) = bucket.pop_front()?;
if bucket.is_empty() {
self.freq_to_keys.remove(&self.min_freq);
}
let (_freq, entry) = self.key_to_entry.remove(&evict_key)?;
Some(entry.value)
}
fn insert_entry(&mut self, key: K, entry: CacheEntry<V>) -> Option<V> {
if self.cap == 0 {
return Some(entry.value);
}
if self.key_to_entry.contains_key(&key) {
let pair = self.key_to_entry.get_mut(&key).expect("confirmed present");
pair.1 = entry;
self.increment_freq(&key);
return None;
}
let evicted = if self.key_to_entry.len() >= self.cap {
self.evict()
} else {
None
};
self.key_to_entry.insert(key.clone(), (1, entry));
self.freq_to_keys
.entry(1)
.or_insert_with(|| LinkedHashMap::with_hasher(ahash::RandomState::default()))
.insert(key, ());
self.min_freq = 1;
evicted
}
pub fn get(&mut self, key: &K) -> Option<&V> {
let expired = self
.key_to_entry
.get(key)
.map(|(_, e)| e.is_expired())
.unwrap_or(false);
if expired {
if let Some((freq, _entry)) = self.key_to_entry.remove(key) {
if let Some(bucket) = self.freq_to_keys.get_mut(&freq) {
bucket.remove(key);
if bucket.is_empty() {
self.freq_to_keys.remove(&freq);
}
}
}
return None;
}
if !self.key_to_entry.contains_key(key) {
return None;
}
self.increment_freq(key);
self.key_to_entry.get(key).map(|(_, e)| &e.value)
}
pub fn put(&mut self, key: K, value: V) -> Option<V> {
self.insert_entry(key, CacheEntry::new(value))
}
pub fn put_with_ttl(&mut self, key: K, value: V, ttl: std::time::Duration) -> Option<V> {
self.insert_entry(key, CacheEntry::with_ttl(value, ttl))
}
#[must_use]
pub fn len(&self) -> usize {
self.key_to_entry.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.key_to_entry.is_empty()
}
#[must_use]
pub fn cap(&self) -> usize {
self.cap
}
#[must_use]
pub fn peek(&self, key: &K) -> Option<&V> {
self.key_to_entry.get(key).and_then(
|(_, e)| {
if e.is_expired() {
None
} else {
Some(&e.value)
}
},
)
}
#[must_use]
pub fn contains_key(&self, key: &K) -> bool {
self.key_to_entry
.get(key)
.map(|(_, e)| !e.is_expired())
.unwrap_or(false)
}
pub fn remove(&mut self, key: &K) -> Option<V> {
if let Some((freq, entry)) = self.key_to_entry.remove(key) {
if let Some(bucket) = self.freq_to_keys.get_mut(&freq) {
bucket.remove(key);
if bucket.is_empty() {
self.freq_to_keys.remove(&freq);
}
}
Some(entry.value)
} else {
None
}
}
pub fn clear(&mut self) {
self.key_to_entry.clear();
self.freq_to_keys.clear();
self.min_freq = 0;
}
pub fn resize(&mut self, new_cap: usize) {
self.cap = new_cap;
while self.cap > 0 && self.key_to_entry.len() > self.cap {
self.evict();
}
}
}
impl<K, V> Cache<K, V> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn get(&mut self, key: &K) -> Option<&V> {
LfuCache::get(self, key)
}
fn put(&mut self, key: K, value: V) -> Option<V> {
LfuCache::put(self, key, value)
}
fn put_with_ttl(&mut self, key: K, value: V, ttl: std::time::Duration) -> Option<V> {
LfuCache::put_with_ttl(self, key, value, ttl)
}
fn len(&self) -> usize {
self.key_to_entry.len()
}
fn cap(&self) -> usize {
self.cap
}
fn remove(&mut self, key: &K) -> Option<V> {
LfuCache::remove(self, key)
}
fn clear(&mut self) {
LfuCache::clear(self);
}
fn peek(&self, key: &K) -> Option<&V> {
LfuCache::peek(self, key)
}
fn contains_key(&self, key: &K) -> bool {
LfuCache::contains_key(self, key)
}
fn resize(&mut self, new_cap: usize) {
LfuCache::resize(self, new_cap);
}
}