use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
pub type Slot = usize;
pub trait KeyIndex {
type Key;
type Error;
fn get(&self, key: &Self::Key) -> Result<Option<Slot>, Self::Error>;
fn insert(&mut self, key: &Self::Key, slot: Slot) -> Result<(), Self::Error>;
fn remove(&mut self, key: &Self::Key) -> Result<(), Self::Error>;
}
#[derive(Debug)]
pub struct HashIndex<K> {
map: HashMap<K, Slot>,
}
impl<K> Default for HashIndex<K> {
fn default() -> Self {
Self {
map: HashMap::new(),
}
}
}
impl<K: Clone + Eq + Hash> KeyIndex for HashIndex<K> {
type Key = K;
type Error = std::convert::Infallible;
fn get(&self, key: &K) -> Result<Option<Slot>, Self::Error> {
Ok(self.map.get(key).copied())
}
fn insert(&mut self, key: &K, slot: Slot) -> Result<(), Self::Error> {
self.map.insert(key.clone(), slot);
Ok(())
}
fn remove(&mut self, key: &K) -> Result<(), Self::Error> {
self.map.remove(key);
Ok(())
}
}
struct Entry<K, V> {
key: K,
value: V,
size: usize,
}
pub struct FifoCache<K, V, I: KeyIndex<Key = K> = HashIndex<K>> {
entries: HashMap<Slot, Entry<K, V>>,
queue: VecDeque<Slot>,
index: I,
next_slot: Slot,
total_size: usize,
}
impl<K, V> FifoCache<K, V, HashIndex<K>>
where
K: Clone + Eq + Hash,
{
pub fn new() -> Self {
Self::with_index(HashIndex::default())
}
}
impl<K, V> Default for FifoCache<K, V, HashIndex<K>>
where
K: Clone + Eq + Hash,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V, I: KeyIndex<Key = K>> FifoCache<K, V, I> {
pub fn with_index(index: I) -> Self {
Self {
entries: HashMap::new(),
queue: VecDeque::new(),
index,
next_slot: 0,
total_size: 0,
}
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn total_size(&self) -> usize {
self.total_size
}
pub fn index(&self) -> &I {
&self.index
}
pub fn contains(&self, key: &K) -> Result<bool, I::Error> {
Ok(self.index.get(key)?.is_some())
}
pub fn get(&self, key: &K) -> Result<Option<&V>, I::Error> {
Ok(self
.index
.get(key)?
.and_then(|slot| self.entries.get(&slot))
.map(|e| &e.value))
}
pub fn keys_oldest_first(&self) -> impl Iterator<Item = &K> {
self.queue
.iter()
.filter_map(move |slot| self.entries.get(slot).map(|e| &e.key))
}
pub fn insert(&mut self, key: K, value: V, size: usize) -> Result<Option<(K, V)>, I::Error> {
let displaced = self.remove(&key)?;
let slot = self.next_slot;
self.next_slot += 1;
self.index.insert(&key, slot)?;
self.entries.insert(slot, Entry { key, value, size });
self.queue.push_back(slot);
self.total_size += size;
Ok(displaced)
}
pub fn remove(&mut self, key: &K) -> Result<Option<(K, V)>, I::Error> {
let slot = match self.index.get(key)? {
Some(slot) => slot,
None => return Ok(None),
};
self.index.remove(key)?;
if let Some(pos) = self.queue.iter().position(|&s| s == slot) {
self.queue.remove(pos);
}
Ok(self.entries.remove(&slot).map(|e| {
self.total_size -= e.size.min(self.total_size);
(e.key, e.value)
}))
}
pub fn evict_until(&mut self, after_cleanup: usize) -> Result<Vec<(K, V)>, I::Error> {
let mut evicted = Vec::new();
while self.total_size > after_cleanup {
match self.pop_oldest()? {
Some(entry) => evicted.push(entry),
None => break,
}
}
Ok(evicted)
}
pub fn drain_oldest(&mut self) -> Result<Vec<(K, V)>, I::Error> {
let mut out = Vec::new();
while let Some(entry) = self.pop_oldest()? {
out.push(entry);
}
Ok(out)
}
pub fn pop_oldest(&mut self) -> Result<Option<(K, V)>, I::Error> {
let slot = match self.queue.pop_front() {
Some(slot) => slot,
None => return Ok(None),
};
let entry = match self.entries.remove(&slot) {
Some(entry) => entry,
None => return Ok(None),
};
self.total_size -= entry.size.min(self.total_size);
self.index.remove(&entry.key)?;
Ok(Some((entry.key, entry.value)))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn counted(n: u32) -> FifoCache<u32, u32> {
let mut c = FifoCache::new();
for i in 0..n {
c.insert(i, i, 1).unwrap();
}
c
}
fn keys(c: &FifoCache<u32, u32>) -> Vec<u32> {
c.keys_oldest_first().copied().collect()
}
#[test]
fn insertion_order_is_preserved() {
let c = counted(3);
assert_eq!(vec![0, 1, 2], keys(&c));
assert_eq!(3, c.total_size());
assert_eq!(Some(&1), c.get(&1).unwrap());
}
#[test]
fn reinserting_moves_to_the_back() {
let mut c = counted(3);
assert_eq!(Some((0, 0)), c.insert(0, 9, 1).unwrap());
assert_eq!(vec![1, 2, 0], keys(&c));
assert_eq!(3, c.len());
assert_eq!(Some(&9), c.get(&0).unwrap());
}
#[test]
fn remove_takes_from_the_middle() {
let mut c = counted(3);
assert_eq!(Some((1, 1)), c.remove(&1).unwrap());
assert_eq!(vec![0, 2], keys(&c));
assert_eq!(2, c.total_size());
assert_eq!(None, c.remove(&1).unwrap());
}
#[test]
fn evict_until_drops_oldest_first() {
let mut c = counted(5);
let evicted: Vec<u32> = c
.evict_until(2)
.unwrap()
.into_iter()
.map(|(k, _)| k)
.collect();
assert_eq!(vec![0, 1, 2], evicted);
assert_eq!(vec![3, 4], keys(&c));
}
#[test]
fn evict_until_accounts_for_sizes() {
let mut c = FifoCache::new();
c.insert(1, "a", 2).unwrap();
c.insert(2, "b", 3).unwrap();
c.insert(3, "c", 4).unwrap();
assert_eq!(9, c.total_size());
let evicted: Vec<u32> = c
.evict_until(5)
.unwrap()
.into_iter()
.map(|(k, _)| k)
.collect();
assert_eq!(vec![1, 2], evicted);
assert_eq!(4, c.total_size());
}
#[test]
fn evict_until_stops_on_empty() {
let mut c = counted(2);
assert_eq!(2, c.evict_until(0).unwrap().len());
assert!(c.is_empty());
assert_eq!(0, c.total_size());
}
#[test]
fn drain_returns_oldest_first() {
let mut c = counted(3);
let drained: Vec<u32> = c
.drain_oldest()
.unwrap()
.into_iter()
.map(|(k, _)| k)
.collect();
assert_eq!(vec![0, 1, 2], drained);
assert!(c.is_empty());
assert_eq!(0, c.total_size());
}
}