use alloc::vec::Vec;
#[derive(Debug, Clone)]
pub struct Dictionary {
arena: Vec<u8>,
offsets: Vec<u32>,
max_entries: usize,
}
impl Dictionary {
#[must_use]
pub const fn new(max_entries: usize) -> Self {
Self {
arena: Vec::new(),
offsets: Vec::new(),
max_entries,
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn add(&mut self, phrase: &[u8]) -> u32 {
if let Some(pos) = (0..self.offsets.len()).position(|i| self.get_entry(i) == phrase) {
return pos as u32;
}
if self.offsets.len() >= self.max_entries {
let first_end = if self.offsets.len() > 1 {
self.offsets[1] as usize
} else {
self.arena.len()
};
self.arena.drain(..first_end);
self.offsets.remove(0);
for off in &mut self.offsets {
*off -= first_end as u32;
}
}
self.offsets.push(self.arena.len() as u32);
self.arena.extend_from_slice(phrase);
(self.offsets.len() - 1) as u32
}
#[must_use]
pub fn lookup(&self, idx: u32) -> Option<&[u8]> {
let i = idx as usize;
if i >= self.offsets.len() {
return None;
}
Some(self.get_entry(i))
}
#[inline]
fn get_entry(&self, i: usize) -> &[u8] {
let start = self.offsets[i] as usize;
let end = if i + 1 < self.offsets.len() {
self.offsets[i + 1] as usize
} else {
self.arena.len()
};
&self.arena[start..end]
}
#[must_use]
pub const fn len(&self) -> usize {
self.offsets.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
}
#[cfg(test)]
#[allow(
clippy::doc_markdown,
clippy::unreadable_literal,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
clippy::uninlined_format_args
)]
mod tests {
use super::*;
#[test]
fn dictionary_basic() {
let mut dict = Dictionary::new(100);
let idx1 = dict.add(b"hello");
let idx2 = dict.add(b"world");
assert_eq!(dict.lookup(idx1), Some(b"hello".as_slice()));
assert_eq!(dict.lookup(idx2), Some(b"world".as_slice()));
}
#[test]
fn dictionary_dedup() {
let mut dict = Dictionary::new(100);
let idx1 = dict.add(b"hello");
let idx2 = dict.add(b"hello");
assert_eq!(idx1, idx2);
assert_eq!(dict.len(), 1);
}
#[test]
fn dictionary_eviction() {
let mut dict = Dictionary::new(3);
dict.add(b"a");
dict.add(b"b");
dict.add(b"c");
dict.add(b"d");
assert_eq!(dict.len(), 3);
}
#[test]
fn dictionary_initial_empty() {
let dict = Dictionary::new(10);
assert!(dict.is_empty());
assert_eq!(dict.len(), 0);
}
#[test]
fn dictionary_not_empty_after_add() {
let mut dict = Dictionary::new(10);
dict.add(b"abc");
assert!(!dict.is_empty());
assert_eq!(dict.len(), 1);
}
#[test]
fn dictionary_lookup_out_of_range() {
let dict = Dictionary::new(10);
assert_eq!(dict.lookup(0), None);
assert_eq!(dict.lookup(100), None);
assert_eq!(dict.lookup(u32::MAX), None);
}
#[test]
fn dictionary_add_empty_phrase() {
let mut dict = Dictionary::new(10);
let idx = dict.add(b"");
assert_eq!(dict.lookup(idx), Some(b"".as_slice()));
}
#[test]
fn dictionary_max_one() {
let mut dict = Dictionary::new(1);
dict.add(b"first");
assert_eq!(dict.len(), 1);
dict.add(b"second");
assert_eq!(dict.len(), 1);
assert_eq!(dict.lookup(0), Some(b"second".as_slice()));
}
#[test]
fn dictionary_re_add_after_eviction() {
let mut dict = Dictionary::new(2);
dict.add(b"A");
dict.add(b"B");
dict.add(b"C");
let idx = dict.add(b"A");
assert_eq!(dict.lookup(idx), Some(b"A".as_slice()));
}
#[test]
fn dictionary_dedup_after_eviction() {
let mut dict = Dictionary::new(3);
dict.add(b"x");
dict.add(b"y");
dict.add(b"z");
dict.add(b"w");
let idx = dict.add(b"y");
assert_eq!(dict.lookup(idx), Some(b"y".as_slice()));
assert_eq!(dict.len(), 3);
}
#[test]
fn dictionary_many_entries() {
let mut dict = Dictionary::new(1000);
for i in 0u32..500 {
let phrase = alloc::format!("entry_{i}");
dict.add(phrase.as_bytes());
}
assert_eq!(dict.len(), 500);
}
#[test]
fn dictionary_clone() {
let mut dict = Dictionary::new(10);
dict.add(b"test");
let dict2 = dict.clone();
assert_eq!(dict2.len(), 1);
assert_eq!(dict2.lookup(0), Some(b"test".as_slice()));
}
#[test]
fn dictionary_debug() {
let dict = Dictionary::new(5);
let s = alloc::format!("{:?}", dict);
assert!(s.contains("Dictionary"));
}
#[test]
fn dictionary_binary_phrase() {
let mut dict = Dictionary::new(10);
let binary = &[0x00, 0xFF, 0x80, 0x7F];
let idx = dict.add(binary);
assert_eq!(dict.lookup(idx), Some(binary.as_slice()));
}
#[test]
fn dictionary_dedup_multiple_times() {
let mut dict = Dictionary::new(100);
for _ in 0..10 {
dict.add(b"same");
}
assert_eq!(dict.len(), 1);
}
#[test]
fn dictionary_eviction_removes_first() {
let mut dict = Dictionary::new(2);
dict.add(b"alpha");
dict.add(b"beta");
dict.add(b"gamma");
assert_eq!(dict.len(), 2);
assert_eq!(dict.lookup(0), Some(b"beta".as_slice()));
assert_eq!(dict.lookup(1), Some(b"gamma".as_slice()));
}
}