#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionLevel {
Fast,
Default,
Best,
}
impl CompressionLevel {
#[must_use]
pub fn effort(self) -> u32 {
match self {
Self::Fast => 1,
Self::Default => 5,
Self::Best => 9,
}
}
}
#[must_use]
pub fn rle_encode(data: &[u8]) -> Vec<u8> {
if data.is_empty() {
return Vec::new();
}
let mut out = Vec::new();
let mut iter = data.iter().copied();
let Some(mut current) = iter.next() else {
return out;
};
let mut count: u8 = 1;
for byte in iter {
if byte == current && count < u8::MAX {
count += 1;
} else {
out.push(count);
out.push(current);
current = byte;
count = 1;
}
}
out.push(count);
out.push(current);
out
}
pub fn rle_decode(data: &[u8]) -> Result<Vec<u8>, String> {
if data.len() % 2 != 0 {
return Err(format!(
"RLE stream length must be even, got {} bytes",
data.len()
));
}
let mut out = Vec::with_capacity(data.len());
let mut i = 0;
while i + 1 < data.len() {
let count = data[i] as usize;
let value = data[i + 1];
for _ in 0..count {
out.push(value);
}
i += 2;
}
Ok(out)
}
#[derive(Debug, Clone, Copy)]
pub struct CompressionStats {
pub original_bytes: u64,
pub compressed_bytes: u64,
}
impl CompressionStats {
#[must_use]
pub fn new(original_bytes: u64, compressed_bytes: u64) -> Self {
Self {
original_bytes,
compressed_bytes,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn ratio(&self) -> f64 {
if self.original_bytes == 0 {
return 1.0;
}
self.compressed_bytes as f64 / self.original_bytes as f64
}
#[must_use]
pub fn space_saved_pct(&self) -> f64 {
(1.0 - self.ratio()) * 100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_effort_ordering() {
assert!(CompressionLevel::Fast.effort() < CompressionLevel::Default.effort());
assert!(CompressionLevel::Default.effort() < CompressionLevel::Best.effort());
}
#[test]
fn test_level_fast_effort() {
assert_eq!(CompressionLevel::Fast.effort(), 1);
}
#[test]
fn test_level_best_effort() {
assert_eq!(CompressionLevel::Best.effort(), 9);
}
#[test]
fn test_rle_encode_empty() {
assert!(rle_encode(&[]).is_empty());
}
#[test]
fn test_rle_encode_single_byte() {
assert_eq!(rle_encode(&[42]), vec![1, 42]);
}
#[test]
fn test_rle_encode_run() {
assert_eq!(rle_encode(&[0xFF, 0xFF, 0xFF, 0xFF]), vec![4, 0xFF]);
}
#[test]
fn test_rle_encode_multiple_runs() {
let input = vec![1, 1, 2, 2, 2];
assert_eq!(rle_encode(&input), vec![2, 1, 3, 2]);
}
#[test]
fn test_rle_encode_no_repetition() {
let input = vec![1, 2, 3];
assert_eq!(rle_encode(&input), vec![1, 1, 1, 2, 1, 3]);
}
#[test]
fn test_rle_decode_empty() {
assert_eq!(
rle_decode(&[]).expect("operation should succeed"),
Vec::<u8>::new()
);
}
#[test]
fn test_rle_decode_single_pair() {
assert_eq!(
rle_decode(&[3, 7]).expect("operation should succeed"),
vec![7, 7, 7]
);
}
#[test]
fn test_rle_roundtrip() {
let original = vec![0u8, 0, 0, 1, 2, 2, 2, 3];
let encoded = rle_encode(&original);
let decoded = rle_decode(&encoded).expect("operation should succeed");
assert_eq!(decoded, original);
}
#[test]
fn test_rle_decode_odd_length_error() {
assert!(rle_decode(&[1]).is_err());
}
#[test]
fn test_stats_ratio_half() {
let s = CompressionStats::new(100, 50);
assert!((s.ratio() - 0.5).abs() < 1e-9);
}
#[test]
fn test_stats_space_saved_pct() {
let s = CompressionStats::new(100, 50);
assert!((s.space_saved_pct() - 50.0).abs() < 1e-9);
}
#[test]
fn test_stats_zero_original() {
let s = CompressionStats::new(0, 0);
assert!((s.ratio() - 1.0).abs() < 1e-9);
assert!((s.space_saved_pct() - 0.0).abs() < 1e-9);
}
#[test]
fn test_stats_expansion() {
let s = CompressionStats::new(10, 20);
assert!(s.ratio() > 1.0);
assert!(s.space_saved_pct() < 0.0);
}
}