use crate::error::{CompressionError, Result};
use ahash::AHashMap;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct DictionaryConfig {
pub max_dict_size: usize,
pub symbol_size: usize,
}
impl Default for DictionaryConfig {
fn default() -> Self {
Self {
max_dict_size: 65536,
symbol_size: 4,
}
}
}
impl DictionaryConfig {
pub fn with_symbol_size(symbol_size: usize) -> Self {
Self {
symbol_size,
..Default::default()
}
}
pub fn with_max_dict_size(mut self, size: usize) -> Self {
self.max_dict_size = size;
self
}
}
pub struct DictionaryCodec {
config: DictionaryConfig,
}
impl DictionaryCodec {
pub fn new() -> Self {
Self {
config: DictionaryConfig::default(),
}
}
pub fn with_config(config: DictionaryConfig) -> Self {
Self { config }
}
pub fn compress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
let symbol_size = self.config.symbol_size;
if input.len() % symbol_size != 0 {
return Err(CompressionError::InvalidBufferSize(format!(
"Input size {} must be multiple of symbol size {}",
input.len(),
symbol_size
)));
}
let num_symbols = input.len() / symbol_size;
let mut dictionary = Vec::new();
let mut dict_map: AHashMap<Vec<u8>, u32> = AHashMap::new();
let mut indices = Vec::with_capacity(num_symbols);
for i in 0..num_symbols {
let start = i * symbol_size;
let end = start + symbol_size;
let symbol = &input[start..end];
let index = if let Some(&idx) = dict_map.get(symbol) {
idx
} else {
if dictionary.len() >= self.config.max_dict_size {
return Err(CompressionError::DictionaryError(
"Dictionary size exceeded".to_string(),
));
}
let idx = (dictionary.len() / symbol_size) as u32;
dictionary.extend_from_slice(symbol);
dict_map.insert(symbol.to_vec(), idx);
idx
};
indices.push(index);
}
let mut output = Vec::new();
output.write_u32::<LittleEndian>(dictionary.len() as u32 / symbol_size as u32)?;
output.extend_from_slice(&dictionary);
if dict_map.len() <= 256 {
output.push(1); for idx in indices {
output.push(idx as u8);
}
} else if dict_map.len() <= 65536 {
output.push(2); for idx in indices {
output.write_u16::<LittleEndian>(idx as u16)?;
}
} else {
output.push(4); for idx in indices {
output.write_u32::<LittleEndian>(idx)?;
}
}
Ok(output)
}
pub fn decompress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
let mut cursor = Cursor::new(input);
let symbol_size = self.config.symbol_size;
let dict_size = cursor.read_u32::<LittleEndian>()? as usize;
let dict_bytes = dict_size * symbol_size;
let pos = cursor.position() as usize;
if pos + dict_bytes > input.len() {
return Err(CompressionError::DictionaryError(
"Invalid dictionary size".to_string(),
));
}
let dictionary = &input[pos..pos + dict_bytes];
cursor.set_position((pos + dict_bytes) as u64);
let index_size = cursor.read_u8()? as usize;
let mut output = Vec::new();
match index_size {
1 => {
while cursor.position() < input.len() as u64 {
let idx = cursor.read_u8()? as usize;
let start = idx * symbol_size;
let end = start + symbol_size;
if end > dictionary.len() {
return Err(CompressionError::DictionaryError(
"Invalid dictionary index".to_string(),
));
}
output.extend_from_slice(&dictionary[start..end]);
}
}
2 => {
while cursor.position() < input.len() as u64 {
let idx = cursor.read_u16::<LittleEndian>()? as usize;
let start = idx * symbol_size;
let end = start + symbol_size;
if end > dictionary.len() {
return Err(CompressionError::DictionaryError(
"Invalid dictionary index".to_string(),
));
}
output.extend_from_slice(&dictionary[start..end]);
}
}
4 => {
while cursor.position() < input.len() as u64 {
let idx = cursor.read_u32::<LittleEndian>()? as usize;
let start = idx * symbol_size;
let end = start + symbol_size;
if end > dictionary.len() {
return Err(CompressionError::DictionaryError(
"Invalid dictionary index".to_string(),
));
}
output.extend_from_slice(&dictionary[start..end]);
}
}
_ => {
return Err(CompressionError::DictionaryError(format!(
"Invalid index size: {}",
index_size
)));
}
}
Ok(output)
}
pub fn count_unique(input: &[u8], symbol_size: usize) -> usize {
if input.is_empty() || input.len() % symbol_size != 0 {
return 0;
}
let mut unique = std::collections::HashSet::new();
for chunk in input.chunks_exact(symbol_size) {
unique.insert(chunk);
}
unique.len()
}
}
impl Default for DictionaryCodec {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dictionary_compress_decompress() {
let config = DictionaryConfig::with_symbol_size(4);
let codec = DictionaryCodec::with_config(config);
let mut data = Vec::new();
for _ in 0..10 {
data.extend_from_slice(&[1u8, 2, 3, 4]);
}
for _ in 0..10 {
data.extend_from_slice(&[5u8, 6, 7, 8]);
}
let compressed = codec.compress(&data).expect("Compression failed");
assert!(compressed.len() < data.len());
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_dictionary_empty_data() {
let codec = DictionaryCodec::new();
let data: &[u8] = b"";
let compressed = codec.compress(data).expect("Compression failed");
assert_eq!(compressed.len(), 0);
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed.len(), 0);
}
#[test]
fn test_dictionary_count_unique() {
let mut data = Vec::new();
data.extend_from_slice(&[1u8, 2, 3, 4]);
data.extend_from_slice(&[1u8, 2, 3, 4]);
data.extend_from_slice(&[5u8, 6, 7, 8]);
let unique = DictionaryCodec::count_unique(&data, 4);
assert_eq!(unique, 2);
}
}