use alloc::vec;
use alloc::vec::Vec;
use super::FnEncoder;
use crate::compressor::core::data::Stream;
use crate::compressor::core::error::NodeError;
use crate::compressor::core::exec::NodeCx;
pub(crate) static FLATPACK: FnEncoder = FnEncoder(encode);
const fn nb_bits(alphabet_size: usize) -> usize {
if alphabet_size <= 1 {
alphabet_size
} else {
1 + (alphabet_size as u32 - 1).ilog2() as usize
}
}
fn flatpack(src: &[u8]) -> (Vec<u8>, Vec<u8>) {
if src.is_empty() {
return (Vec::new(), Vec::new());
}
let mut present = [false; 256];
for &byte in src {
present[usize::from(byte)] = true;
}
let mut symbol_map = [0u8; 256];
let mut alphabet = Vec::new();
for (symbol, _) in present.iter().enumerate().filter(|&(_, &p)| p) {
symbol_map[symbol] = alphabet.len() as u8;
alphabet.push(symbol as u8);
}
let bits = nb_bits(alphabet.len());
let packed_size = 1 + bits * src.len() / 8;
let mut packed = vec![0u8; packed_size + 8];
let (groups, tail) = src.as_chunks::<8>();
let mut at = 0usize;
for group in groups {
let mut word = 0u64;
for (i, &byte) in group.iter().enumerate() {
word |= u64::from(symbol_map[usize::from(byte)]) << (i * bits);
}
packed[at..at + 8].copy_from_slice(&word.to_le_bytes());
at += bits;
}
let mut state = 0u64;
let mut pending = 0usize;
for &byte in tail {
state |= u64::from(symbol_map[usize::from(byte)]) << pending;
pending += bits;
}
state |= 1 << pending;
let last = pending / 8 + 1;
packed[at..at + last].copy_from_slice(&state.to_le_bytes()[..last]);
packed.truncate(packed_size);
(alphabet, packed)
}
fn encode<'a>(cx: &mut NodeCx<'_, 'a>, inputs: &[Stream<'a>]) -> Result<(), NodeError> {
let (alphabet, packed) = flatpack(inputs[0].bytes());
cx.emit(0, 1, alphabet)?;
cx.emit(1, 1, packed)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{flatpack, nb_bits};
use alloc::vec;
#[test]
fn bit_counts_follow_the_alphabet_size() {
assert_eq!(
[0, 1, 2, 3, 4, 5, 128, 129, 256].map(nb_bits),
[0, 1, 1, 2, 2, 3, 7, 8, 8]
);
}
#[test]
fn empty_input_has_empty_outputs() {
assert_eq!(flatpack(&[]), (vec![], vec![]));
}
#[test]
fn one_symbol_takes_a_bit_per_byte_before_the_terminator() {
assert_eq!(flatpack(&[7, 7, 7]), (vec![7], vec![8]));
}
#[test]
fn indices_are_packed_low_bits_first_with_a_terminator() {
let (alphabet, packed) = flatpack(&[3, 1, 2]);
assert_eq!(alphabet, [1, 2, 3]);
assert_eq!(packed, [0b0101_0010]);
}
#[test]
fn a_full_alphabet_uses_a_byte_per_symbol() {
let src: alloc::vec::Vec<u8> = (0..=255).rev().collect();
let (alphabet, packed) = flatpack(&src);
assert_eq!(alphabet.len(), 256);
assert_eq!(packed.len(), 257);
assert_eq!(&packed[..3], &[255, 254, 253]);
assert_eq!(packed[256], 1);
}
fn reference(src: &[u8]) -> alloc::vec::Vec<u8> {
let mut present = [false; 256];
for &byte in src {
present[usize::from(byte)] = true;
}
let mut map = [0u8; 256];
let mut size = 0u8;
for symbol in 0..256 {
if present[symbol] {
map[symbol] = size;
size = size.wrapping_add(1);
}
}
let distinct = present.iter().filter(|&&p| p).count();
let bits = nb_bits(distinct);
let (mut state, mut pending) = (0usize, 0usize);
let mut out = alloc::vec::Vec::new();
for &byte in src {
state |= usize::from(map[usize::from(byte)]) << pending;
pending += bits;
if pending >= 8 {
out.push(state as u8);
pending -= 8;
state >>= 8;
}
}
state |= 1 << pending;
out.push(state as u8);
out
}
#[test]
fn grouped_packing_matches_the_one_index_reference() {
for distinct in [1usize, 2, 3, 4, 5, 16, 17, 100, 128, 129, 256] {
for len in (0..40).chain([63, 64, 65, 200]) {
let src: alloc::vec::Vec<u8> = (0..len)
.map(|i| ((i * 7 + i / 3) % distinct) as u8)
.collect();
if src.is_empty() {
continue;
}
assert_eq!(flatpack(&src).1, reference(&src), "{distinct} {len}");
}
}
}
}