#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LookupTable {
table: [u16; 256],
reverse: [u16; 256],
bucket_uses: [u16; 256],
limit: u16,
}
impl LookupTable {
pub fn new(kind: LookupKind, limit: u16) -> Self {
let mut t = Self {
table: [0u16; 256],
reverse: [0u16; 256],
bucket_uses: [0u16; 256],
limit,
};
t.reset_table(kind);
t
}
fn reset_table(&mut self, kind: LookupKind) {
for (i, slot) in self.table.iter_mut().enumerate() {
let group = i / 32;
let bucket = 7 - group as u16;
let value: u16 = match kind {
LookupKind::Identity => i as u16,
LookupKind::Complement => (256u16.wrapping_sub(i as u16)) & 0xFF,
};
*slot = (value << 8) | bucket;
}
for b in 0u16..8 {
self.reverse[b as usize] = (7 - b) * 32;
}
for u in self.bucket_uses.iter_mut() {
*u = 0;
}
}
pub fn lookup(&mut self, kind: LookupKind, index: u8) -> u8 {
let idx = index as usize;
let entry = self.table[idx];
let value_byte = (entry >> 8) as u8;
let bucket = (entry & 0xFF) as usize;
let target = self.reverse[bucket] as usize;
if target != idx {
self.table.swap(idx, target);
}
let bumped = self.table[target];
let bumped_low = bumped & 0xFF;
let new_low = bumped_low.saturating_add(1).min(0xFF);
self.table[target] = (bumped & 0xFF00) | new_low;
self.reverse[bucket] = self.reverse[bucket].wrapping_add(1);
self.bucket_uses[bucket] = self.bucket_uses[bucket].saturating_add(1);
if self.bucket_uses[bucket] >= self.limit {
self.reset_table(kind);
}
value_byte
}
#[cfg(test)]
pub fn raw_table(&self) -> [u16; 256] {
self.table
}
#[cfg(test)]
pub fn raw_reverse(&self) -> [u16; 256] {
self.reverse
}
}
#[derive(Debug, Clone, Copy)]
pub enum LookupKind {
Identity,
Complement,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_initial_values() {
let t = LookupTable::new(LookupKind::Identity, 32);
for i in 0..256u16 {
let v = t.raw_table()[i as usize] >> 8;
assert_eq!(v, i, "slot {i}");
}
}
#[test]
fn complement_initial_values() {
let t = LookupTable::new(LookupKind::Complement, 32);
for i in 0..256u16 {
let v = t.raw_table()[i as usize] >> 8;
let expected = (256u16.wrapping_sub(i)) & 0xFF;
assert_eq!(v, expected, "slot {i}");
}
}
#[test]
fn group_bucket_assignment() {
let t = LookupTable::new(LookupKind::Identity, 32);
for i in 0..256u16 {
let group = i / 32;
let bucket_expected = 7 - group;
let bucket_actual = t.raw_table()[i as usize] & 0xFF;
assert_eq!(bucket_actual, bucket_expected, "slot {i}");
}
}
#[test]
fn reverse_pointers_start_at_group_start() {
let t = LookupTable::new(LookupKind::Identity, 32);
for b in 0u16..8 {
let expected_start = (7 - b) * 32;
assert_eq!(t.raw_reverse()[b as usize], expected_start, "bucket {b}");
}
}
#[test]
fn lookup_returns_value_byte() {
let mut t = LookupTable::new(LookupKind::Identity, 32);
assert_eq!(t.lookup(LookupKind::Identity, 5), 5);
}
#[test]
fn lookup_promotes_within_bucket() {
let mut t = LookupTable::new(LookupKind::Identity, 32);
assert_eq!(t.raw_reverse()[4], 96);
let got = t.lookup(LookupKind::Identity, 100);
assert_eq!(got, 100);
let s96 = t.raw_table()[96];
assert_eq!(s96 >> 8, 100);
let s100 = t.raw_table()[100];
assert_eq!(s100 >> 8, 96);
assert_eq!(t.raw_reverse()[4], 97);
}
#[test]
fn lookup_resets_when_limit_reached() {
let mut t = LookupTable::new(LookupKind::Identity, 2);
let _ = t.lookup(LookupKind::Identity, 96); let _ = t.lookup(LookupKind::Identity, 97);
let fresh = LookupTable::new(LookupKind::Identity, 2);
assert_eq!(t.raw_table(), fresh.raw_table());
assert_eq!(t.raw_reverse(), fresh.raw_reverse());
}
#[test]
fn first_lookup_of_each_bucket_is_stable() {
let mut t = LookupTable::new(LookupKind::Identity, 32);
assert_eq!(t.lookup(LookupKind::Identity, 0), 0);
let s0 = t.raw_table()[0];
assert_eq!(s0 >> 8, 0);
assert_eq!(s0 & 0xFF, 8); }
}