use std::collections::HashMap;
use std::hash::Hash;
const NIL: usize = usize::MAX;
struct Node<K, V> {
key: K,
val: V,
prev: usize,
next: usize,
}
pub(crate) struct OrderedMap<K, V> {
map: HashMap<K, usize>,
slots: Vec<Option<Node<K, V>>>,
free: Vec<usize>,
front: usize,
back: usize,
}
impl<K: Hash + Eq + Clone, V> OrderedMap<K, V> {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(capacity),
slots: Vec::with_capacity(capacity),
free: Vec::new(),
front: NIL,
back: NIL,
}
}
pub(crate) fn len(&self) -> usize {
self.map.len()
}
pub(crate) fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
pub(crate) fn get(&self, key: &K) -> Option<&V> {
let idx = *self.map.get(key)?;
self.slots
.get(idx)
.and_then(|slot| slot.as_ref())
.map(|node| &node.val)
}
pub(crate) fn access(&mut self, key: &K, promote: bool) -> Option<&V> {
let idx = *self.map.get(key)?;
if promote {
self.detach(idx);
self.attach_front(idx);
}
self.slots
.get(idx)
.and_then(|slot| slot.as_ref())
.map(|node| &node.val)
}
pub(crate) fn update_value(&mut self, key: &K, val: V) {
if let Some(&idx) = self.map.get(key) {
if let Some(node) = self.slots[idx].as_mut() {
node.val = val;
}
}
}
fn links(&self, idx: usize) -> (usize, usize) {
match self.slots.get(idx).and_then(|slot| slot.as_ref()) {
Some(node) => (node.prev, node.next),
None => (NIL, NIL),
}
}
fn detach(&mut self, idx: usize) {
let (prev, next) = self.links(idx);
if prev != NIL {
if let Some(node) = self.slots[prev].as_mut() {
node.next = next;
}
} else {
self.front = next;
}
if next != NIL {
if let Some(node) = self.slots[next].as_mut() {
node.prev = prev;
}
} else {
self.back = prev;
}
}
fn attach_front(&mut self, idx: usize) {
let old_front = self.front;
if let Some(node) = self.slots[idx].as_mut() {
node.prev = NIL;
node.next = old_front;
}
if old_front != NIL {
if let Some(node) = self.slots[old_front].as_mut() {
node.prev = idx;
}
} else {
self.back = idx;
}
self.front = idx;
}
pub(crate) fn insert_front(&mut self, key: K, val: V) {
if let Some(&idx) = self.map.get(&key) {
if let Some(node) = self.slots[idx].as_mut() {
node.val = val;
}
self.detach(idx);
self.attach_front(idx);
return;
}
let node = Node {
key: key.clone(),
val,
prev: NIL,
next: NIL,
};
let idx = match self.free.pop() {
Some(reused) => {
self.slots[reused] = Some(node);
reused
}
None => {
self.slots.push(Some(node));
self.slots.len() - 1
}
};
let _prev = self.map.insert(key, idx);
self.attach_front(idx);
}
pub(crate) fn move_to_front(&mut self, key: &K) {
if let Some(&idx) = self.map.get(key) {
self.detach(idx);
self.attach_front(idx);
}
}
pub(crate) fn remove(&mut self, key: &K) -> Option<V> {
let idx = self.map.remove(key)?;
self.detach(idx);
let node = self.slots[idx].take();
self.free.push(idx);
node.map(|node| node.val)
}
pub(crate) fn pop_back(&mut self) -> Option<(K, V)> {
if self.back == NIL {
return None;
}
let idx = self.back;
let key = self
.slots
.get(idx)
.and_then(|slot| slot.as_ref())
.map(|node| node.key.clone())?;
self.detach(idx);
let _removed = self.map.remove(&key);
let node = self.slots[idx].take();
self.free.push(idx);
node.map(|node| (key, node.val))
}
pub(crate) fn clear(&mut self) {
self.map.clear();
self.slots.clear();
self.free.clear();
self.front = NIL;
self.back = NIL;
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn insert_get_contains() {
let mut m: OrderedMap<u32, u32> = OrderedMap::with_capacity(4);
m.insert_front(1, 10);
m.insert_front(2, 20);
assert!(m.contains(&1));
assert_eq!(m.get(&2), Some(&20));
assert_eq!(m.get(&3), None);
assert_eq!(m.len(), 2);
}
#[test]
fn pop_back_is_least_recently_touched() {
let mut m = OrderedMap::with_capacity(4);
m.insert_front(1u32, 10u32);
m.insert_front(2, 20);
m.insert_front(3, 30);
assert_eq!(m.pop_back(), Some((1, 10)));
assert_eq!(m.pop_back(), Some((2, 20)));
assert_eq!(m.pop_back(), Some((3, 30)));
assert_eq!(m.pop_back(), None);
}
#[test]
fn move_to_front_changes_eviction_order() {
let mut m = OrderedMap::with_capacity(4);
m.insert_front(1u32, 10u32);
m.insert_front(2, 20);
m.move_to_front(&1);
assert_eq!(m.pop_back(), Some((2, 20)));
}
#[test]
fn remove_arbitrary_and_reuse_slot() {
let mut m = OrderedMap::with_capacity(4);
m.insert_front(1u32, 10u32);
m.insert_front(2, 20);
m.insert_front(3, 30);
assert_eq!(m.remove(&2), Some(20));
assert_eq!(m.remove(&2), None);
assert_eq!(m.len(), 2);
assert_eq!(m.pop_back(), Some((1, 10)));
assert_eq!(m.pop_back(), Some((3, 30)));
}
#[test]
fn insert_existing_updates_and_promotes() {
let mut m = OrderedMap::with_capacity(4);
m.insert_front(1u32, 10u32);
m.insert_front(2, 20);
m.insert_front(1, 11); assert_eq!(m.get(&1), Some(&11));
assert_eq!(m.len(), 2);
assert_eq!(m.pop_back(), Some((2, 20)));
}
#[test]
fn update_value_does_not_reorder() {
let mut m = OrderedMap::with_capacity(4);
m.insert_front(1u32, 10u32);
m.insert_front(2, 20);
m.update_value(&1, 99);
assert_eq!(m.get(&1), Some(&99));
assert_eq!(m.pop_back(), Some((1, 99)));
}
#[test]
fn clear_then_reuse() {
let mut m = OrderedMap::with_capacity(2);
m.insert_front(1u32, 1u32);
m.clear();
assert_eq!(m.len(), 0);
assert_eq!(m.get(&1), None);
m.insert_front(2, 2);
assert_eq!(m.get(&2), Some(&2));
}
#[test]
fn single_element_pop_resets_ends() {
let mut m = OrderedMap::with_capacity(2);
m.insert_front(1u32, 1u32);
assert_eq!(m.pop_back(), Some((1, 1)));
assert_eq!(m.pop_back(), None);
m.insert_front(2, 2);
assert_eq!(m.get(&2), Some(&2));
}
}