#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#[derive(Clone, Debug)]
pub struct CodebookEntry {
pub length: u8,
pub value: Vec<f32>,
}
#[derive(Clone, Debug)]
pub struct Codebook {
pub entries: usize,
pub table: Vec<CodebookEntry>,
pub dimensions: usize,
}
impl Codebook {
#[must_use]
pub fn from_lengths_and_values(lengths: &[u8], values: &[f32]) -> Self {
let entries = lengths.len();
let table = (0..entries)
.map(|i| CodebookEntry {
length: lengths[i],
value: vec![values[i.min(values.len() - 1)]],
})
.collect();
Self {
entries,
table,
dimensions: 1,
}
}
#[must_use]
pub fn lookup(&self, index: usize) -> Option<&[f32]> {
self.table
.get(index)
.filter(|e| e.length > 0)
.map(|e| e.value.as_slice())
}
#[must_use]
pub fn average_code_length(&self) -> f64 {
let active: Vec<u8> = self
.table
.iter()
.filter(|e| e.length > 0)
.map(|e| e.length)
.collect();
if active.is_empty() {
return 0.0;
}
active.iter().map(|&l| f64::from(l)).sum::<f64>() / active.len() as f64
}
#[must_use]
pub fn kraft_inequality_satisfied(&self) -> bool {
let sum: f64 = self
.table
.iter()
.filter(|e| e.length > 0)
.map(|e| 2.0f64.powi(-(e.length as i32)))
.sum();
sum <= 1.0 + 1e-9
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_book() -> Codebook {
let lengths = [1u8, 2, 3, 3];
let values = [0.0f32, 1.0, 2.0, 3.0];
Codebook::from_lengths_and_values(&lengths, &values)
}
#[test]
fn test_codebook_entry_count() {
let book = simple_book();
assert_eq!(book.entries, 4);
}
#[test]
fn test_codebook_lookup_valid() {
let book = simple_book();
let v = book.lookup(0).expect("entry 0 should exist");
assert!((v[0] - 0.0).abs() < 1e-6);
}
#[test]
fn test_codebook_lookup_out_of_range() {
let book = simple_book();
assert!(book.lookup(10).is_none());
}
#[test]
fn test_codebook_average_length() {
let book = simple_book();
let avg = book.average_code_length();
assert!((avg - 2.25).abs() < 1e-9, "Expected 2.25, got {avg}");
}
#[test]
fn test_codebook_kraft_inequality() {
let book = simple_book();
assert!(book.kraft_inequality_satisfied());
}
#[test]
fn test_codebook_unused_entry_length_zero() {
let lengths = [0u8, 2, 0, 3];
let values = [0.0f32; 4];
let book = Codebook::from_lengths_and_values(&lengths, &values);
assert!(book.lookup(0).is_none()); assert!(book.lookup(1).is_some());
assert!(book.lookup(2).is_none());
}
#[test]
fn test_codebook_dimension_is_one() {
let book = simple_book();
assert_eq!(book.dimensions, 1);
}
}