use crate::codec::Codec;
use crate::codec::CODEC_BLOSC2_SHUFFLE_LZ4;
use crate::error::CoreError;
use omnizip_filters::Filter;
pub struct ShuffleLz4Codec {
item_size: usize,
}
impl ShuffleLz4Codec {
#[must_use]
pub fn new(item_size: usize) -> Self {
let item_size = if [1, 2, 4, 8].contains(&item_size) {
item_size
} else {
4 };
Self { item_size }
}
#[must_use]
pub fn float32() -> Self {
Self::new(4)
}
#[must_use]
#[allow(dead_code)]
pub fn float64() -> Self {
Self::new(8)
}
}
impl Default for ShuffleLz4Codec {
fn default() -> Self {
Self::float32()
}
}
impl Codec for ShuffleLz4Codec {
fn id(&self) -> u8 {
CODEC_BLOSC2_SHUFFLE_LZ4
}
fn name(&self) -> &'static str {
"shuffle+lz4"
}
fn min_compress_size(&self) -> usize {
512
}
fn compress(&self, plaintext: &[u8]) -> Result<Vec<u8>, CoreError> {
let filter = omnizip_filters::shuffle::ByteShuffle::new(self.item_size);
crate::codec::composite::filter_then_compress(plaintext, &filter, crate::codec::CODEC_LZ4)
}
fn decompress(&self, compressed: &[u8], _expected_len: u32) -> Result<Vec<u8>, CoreError> {
let filter = omnizip_filters::shuffle::ByteShuffle::new(self.item_size);
crate::codec::composite::decompress_then_filter(
compressed,
&filter,
crate::codec::CODEC_LZ4,
"shuffle+lz4",
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_float32_array() {
let samples: Vec<f32> = (0..1024).map(|i| (i as f32 * 0.1).sin()).collect();
let bytes: Vec<u8> = samples.iter().flat_map(|f| f.to_le_bytes()).collect();
let codec = ShuffleLz4Codec::float32();
let compressed = codec.compress(&bytes).expect("compress");
let recovered = codec
.decompress(&compressed, bytes.len() as u32)
.expect("decompress");
assert_eq!(recovered, bytes);
}
#[test]
fn beats_plain_lz4_on_smooth_floats() {
let samples: Vec<f32> = (0..4096).map(|i| (i as f32 * 0.01).sin()).collect();
let bytes: Vec<u8> = samples.iter().flat_map(|f| f.to_le_bytes()).collect();
let codec = ShuffleLz4Codec::float32();
let shuffled_compressed = codec.compress(&bytes).expect("shuffle+lz4");
let plain = crate::codec::compress(crate::codec::CODEC_LZ4, &bytes).expect("plain lz4");
let ratio = shuffled_compressed.len() as f64 / plain.len().max(1) as f64;
assert!(
ratio <= 1.05,
"shuffle+lz4 ({}) should be within 5% of plain LZ4 ({}) on smooth floats (ratio {:.3})",
shuffled_compressed.len(),
plain.len(),
ratio
);
}
}