use std::collections::HashMap;
use std::hash::Hash;
#[derive(Clone, Debug)]
pub struct ValueLexicon<T: Eq + Hash + Clone> {
values: Vec<T>,
index: HashMap<T, u32>,
}
impl<T: Eq + Hash + Clone> ValueLexicon<T> {
pub fn new() -> Self {
ValueLexicon {
values: Vec::new(),
index: HashMap::new(),
}
}
#[expect(clippy::cast_possible_truncation, reason = "IDs always fit in u32")]
pub fn add(&mut self, value: T) -> u32 {
if let Some(&id) = self.index.get(&value) {
return id;
}
let id = self.values.len() as u32;
self.index.insert(value.clone(), id);
self.values.push(value);
id
}
#[expect(clippy::cast_possible_truncation, reason = "IDs always fit in u32")]
pub fn size(&self) -> u32 {
self.values.len() as u32
}
pub fn value(&self, id: u32) -> &T {
&self.values[id as usize]
}
pub fn clear(&mut self) {
self.values.clear();
self.index.clear();
}
}
impl<T: Eq + Hash + Clone> Default for ValueLexicon<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_duplicate_values() {
let mut lex = ValueLexicon::new();
assert_eq!(0, lex.add(5_i64));
assert_eq!(1, lex.add(0_i64));
assert_eq!(1, lex.add(0_i64));
assert_eq!(2, lex.add(-3_i64));
assert_eq!(0, lex.add(5_i64));
assert_eq!(1, lex.add(0_i64));
assert_eq!(3, lex.add(0x7fffffffffffffff_i64));
assert_eq!(4, lex.add(-0x8000000000000000_i64));
assert_eq!(3, lex.add(0x7fffffffffffffff_i64));
assert_eq!(4, lex.add(-0x8000000000000000_i64));
assert_eq!(5, lex.size());
assert_eq!(&5_i64, lex.value(0));
assert_eq!(&0_i64, lex.value(1));
assert_eq!(&-3_i64, lex.value(2));
assert_eq!(&0x7fffffffffffffff_i64, lex.value(3));
assert_eq!(&-0x8000000000000000_i64, lex.value(4));
}
#[test]
fn test_clear() {
let mut lex = ValueLexicon::new();
assert_eq!(0, lex.add(1_i64));
assert_eq!(1, lex.add(2_i64));
assert_eq!(0, lex.add(1_i64));
lex.clear();
assert_eq!(0, lex.add(2_i64));
assert_eq!(1, lex.add(1_i64));
assert_eq!(0, lex.add(2_i64));
}
#[test]
fn test_copy_constructor() {
let mut original = ValueLexicon::new();
assert_eq!(0, original.add(5_i64));
let mut lex = original.clone();
drop(original);
assert_eq!(1, lex.add(10_i64));
assert_eq!(&5_i64, lex.value(0));
assert_eq!(&10_i64, lex.value(1));
}
#[test]
fn test_move_constructor() {
let mut original = ValueLexicon::new();
assert_eq!(0, original.add(5_i64));
let mut lex = original; assert_eq!(1, lex.add(10_i64));
assert_eq!(&5_i64, lex.value(0));
assert_eq!(&10_i64, lex.value(1));
}
#[test]
fn test_copy_assignment_operator() {
let mut original = ValueLexicon::new();
assert_eq!(0, original.add(5_i64));
let mut lex = ValueLexicon::new();
assert_eq!(0, lex.add(10_i64));
assert_eq!(1, lex.add(15_i64));
lex = original.clone();
drop(original);
assert_eq!(1, lex.add(20_i64));
assert_eq!(&5_i64, lex.value(0));
assert_eq!(&20_i64, lex.value(1));
}
#[test]
fn test_move_assignment_operator() {
let mut original = ValueLexicon::new();
assert_eq!(0, original.add(5_i64));
let mut lex = ValueLexicon::new();
assert_eq!(0, lex.add(10_i64));
assert_eq!(1, lex.add(15_i64));
lex = original; assert_eq!(1, lex.add(20_i64));
assert_eq!(&5_i64, lex.value(0));
assert_eq!(&20_i64, lex.value(1));
}
#[test]
fn test_string_values() {
let mut lex = ValueLexicon::new();
let cat_id = lex.add("cat".to_string());
let dog_id = lex.add("dog".to_string());
assert_eq!(cat_id, lex.add("cat".to_string()));
assert_ne!(cat_id, dog_id);
assert_eq!("cat", lex.value(cat_id).as_str());
assert_eq!("dog", lex.value(dog_id).as_str());
assert_eq!(2, lex.size());
}
#[test]
fn test_empty_lexicon() {
let lex = ValueLexicon::<i64>::new();
assert_eq!(0, lex.size());
}
#[test]
#[expect(clippy::cast_sign_loss, reason = "test values are non-negative")]
fn test_sequential_ids() {
let mut lex = ValueLexicon::new();
for i in 0..100_i32 {
assert_eq!(i as u32, lex.add(i));
}
assert_eq!(100, lex.size());
for i in 0..100_i32 {
assert_eq!(&i, lex.value(i as u32));
}
}
#[test]
fn test_default() {
let lex = ValueLexicon::<i32>::default();
assert_eq!(0, lex.size());
}
}