use std::collections::HashMap;
use std::hash::Hash;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Seating {
Fresh(usize),
Existing(usize),
Full,
}
impl Seating {
pub fn index(self) -> Option<usize> {
match self {
Seating::Fresh(index) | Seating::Existing(index) => Some(index),
Seating::Full => None,
}
}
pub fn is_fresh(self) -> bool {
matches!(self, Seating::Fresh(_))
}
}
#[derive(Clone, Debug, Default)]
pub struct SeatTable<Key: Eq + Hash + Clone> {
occupied: HashMap<Key, usize>,
free: Vec<usize>,
capacity: usize,
}
impl<Key: Eq + Hash + Clone> SeatTable<Key> {
pub fn new(capacity: usize) -> Self {
Self {
occupied: HashMap::new(),
free: (0..capacity).collect(),
capacity,
}
}
pub fn seat(&mut self, key: Key) -> Seating {
if let Some(index) = self.occupied.get(&key) {
return Seating::Existing(*index);
}
match self.free.pop() {
Some(index) => {
self.occupied.insert(key, index);
Seating::Fresh(index)
}
None => Seating::Full,
}
}
pub fn unseat(&mut self, key: &Key) -> Option<usize> {
let index = self.occupied.remove(key)?;
self.free.push(index);
Some(index)
}
pub fn seat_of(&self, key: &Key) -> Option<usize> {
self.occupied.get(key).copied()
}
pub fn occupants(&self) -> impl Iterator<Item = (&Key, usize)> + '_ {
self.occupied.iter().map(|(key, seat)| (key, *seat))
}
pub fn keys(&self) -> impl Iterator<Item = &Key> + '_ {
self.occupied.keys()
}
pub fn by_seat(&self) -> HashMap<usize, Key> {
self.occupied.iter().map(|(key, seat)| (*seat, key.clone())).collect()
}
pub fn occupied_count(&self) -> usize {
self.occupied.len()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn is_full(&self) -> bool {
self.free.is_empty()
}
pub fn clear(&mut self) {
self.occupied.clear();
self.free = (0..self.capacity).collect();
}
pub fn reseat_all(&mut self, capacity: usize) -> Vec<(Key, usize)> {
let previous: Vec<Key> = self.occupied.keys().cloned().collect();
self.capacity = capacity;
self.occupied.clear();
self.free = (0..capacity).collect();
let mut reseated = Vec::new();
for key in previous {
if let Some(index) = self.free.pop() {
self.occupied.insert(key.clone(), index);
reseated.push((key, index));
}
}
reseated
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_joiner_gets_a_seat_and_a_rejoin_gets_the_same_one() {
let mut seats: SeatTable<u64> = SeatTable::new(4);
let first = seats.seat(1);
assert!(first.is_fresh());
let again = seats.seat(1);
assert_eq!(again, Seating::Existing(first.index().unwrap()));
assert_eq!(seats.occupied_count(), 1, "a duplicate join is not a second occupant");
}
#[test]
fn a_new_occupant_is_reported_as_fresh_so_stale_seat_state_is_reset() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
let mut baseline = vec![0u32; 2];
let seat = seats.seat(1).index().unwrap();
for _ in 0..150 {
baseline.iter_mut().for_each(|b| *b += 1);
}
seats.unseat(&1);
for _ in 0..150 {
baseline.iter_mut().for_each(|b| *b += 1);
}
match seats.seat(2) {
Seating::Fresh(index) => {
assert_eq!(index, seat, "the freed seat is handed out again");
baseline[index] = 0;
}
other => panic!("a new key in a freed seat must be fresh, got {other:?}"),
}
assert_eq!(baseline[seat], 0, "the new occupant inherited the old one's history");
}
#[test]
fn a_full_world_refuses_rather_than_panicking() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
assert!(seats.seat(1).is_fresh());
assert!(seats.seat(2).is_fresh());
assert_eq!(seats.seat(3), Seating::Full);
assert!(seats.is_full());
assert_eq!(seats.seat(3).index(), None);
}
#[test]
fn an_unseated_seat_is_handed_out_again() {
let mut seats: SeatTable<u64> = SeatTable::new(1);
let taken = seats.seat(1).index().unwrap();
assert_eq!(seats.seat(2), Seating::Full);
assert_eq!(seats.unseat(&1), Some(taken));
assert_eq!(seats.seat(2), Seating::Fresh(taken));
}
#[test]
fn unseating_a_stranger_is_not_an_error() {
let mut seats: SeatTable<u64> = SeatTable::new(2);
seats.seat(1);
assert!(seats.unseat(&1).is_some());
assert_eq!(seats.unseat(&1), None);
assert_eq!(seats.unseat(&99), None);
assert_eq!(seats.occupied_count(), 0);
}
#[test]
fn rebuilding_the_world_reseats_everyone_who_still_fits() {
let mut seats: SeatTable<u64> = SeatTable::new(4);
for key in 1..=3 {
seats.seat(key);
}
let reseated = seats.reseat_all(8);
assert_eq!(reseated.len(), 3, "everybody who was connected is in the new world");
assert_eq!(seats.capacity(), 8);
for (key, seat) in &reseated {
assert_eq!(seats.seat_of(key), Some(*seat));
}
let squeezed = seats.reseat_all(2);
assert_eq!(squeezed.len(), 2);
assert!(squeezed.iter().all(|(_, seat)| *seat < 2));
assert_eq!(seats.occupied_count(), 2);
}
#[test]
fn every_seat_index_stays_inside_the_capacity() {
let mut seats: SeatTable<u64> = SeatTable::new(3);
for key in 1..=3 {
let index = seats.seat(key).index().unwrap();
assert!(index < 3, "{index} is outside a capacity of 3");
}
assert_eq!(seats.by_seat().len(), 3, "each seat is held by exactly one key");
}
}