use std::collections::HashMap;
pub const MAX_CODE_BITS: u8 = 12;
struct BitWriter {
out: Vec<u8>,
bits: u32,
count: u8,
}
impl BitWriter {
fn new() -> BitWriter {
BitWriter {
out: Vec::new(),
bits: 0,
count: 0,
}
}
fn write(&mut self, code: u16, width: u8) {
self.bits |= u32::from(code) << self.count;
self.count += width;
while self.count >= 8 {
self.out.push((self.bits & 0xFF) as u8);
self.bits >>= 8;
self.count -= 8;
}
}
fn finish(mut self) -> Vec<u8> {
if self.count > 0 {
self.out.push((self.bits & 0xFF) as u8);
}
self.out
}
}
pub fn compress(symbols: &[u8], min_code_bits: u8) -> Vec<u8> {
let clear = 1u16 << min_code_bits;
let end = clear + 1;
let mut bits = BitWriter::new();
let mut width = min_code_bits + 1;
let mut dict: HashMap<(u16, u8), u16> = HashMap::new();
let mut next = end + 1;
bits.write(clear, width);
let mut symbols = symbols.iter().copied();
let Some(first) = symbols.next() else {
bits.write(end, width);
return bits.finish();
};
let mut prefix = u16::from(first);
for symbol in symbols {
if let Some(&code) = dict.get(&(prefix, symbol)) {
prefix = code;
continue;
}
bits.write(prefix, width);
if next < 1 << MAX_CODE_BITS {
dict.insert((prefix, symbol), next);
next += 1;
if next > (1u16 << width) && width < MAX_CODE_BITS {
width += 1;
}
} else {
bits.write(clear, width);
dict.clear();
next = end + 1;
width = min_code_bits + 1;
}
prefix = u16::from(symbol);
}
bits.write(prefix, width);
bits.write(end, width);
bits.finish()
}
const MAX_OUTPUT: usize = 1 << 22;
pub fn decompress(data: &[u8], min_code_bits: u8) -> Option<Vec<u8>> {
decode(data, min_code_bits).map(|(out, _)| out)
}
pub(crate) fn decode(data: &[u8], min_code_bits: u8) -> Option<(Vec<u8>, usize)> {
let clear = 1u16 << min_code_bits;
let end = clear + 1;
let mut width = min_code_bits + 1;
let mut table: Vec<Vec<u8>> = Vec::new();
let reset = |table: &mut Vec<Vec<u8>>| {
table.clear();
for index in 0..=u16::from(u8::MAX) {
table.push(vec![index as u8]);
if index == clear - 1 {
break;
}
}
table.push(Vec::new()); table.push(Vec::new()); };
reset(&mut table);
let (mut acc, mut count, mut at) = (0u32, 0u8, 0usize);
let mut out = Vec::new();
let mut prev: Option<u16> = None;
let mut restarts = 0usize;
let mut started = false;
loop {
while count < width && at < data.len() {
acc |= u32::from(data[at]) << count;
count += 8;
at += 1;
}
if count < width {
break;
}
let code = (acc & ((1u32 << width) - 1)) as u16;
acc >>= width;
count -= width;
if code == clear {
if started {
restarts += 1;
}
started = true;
reset(&mut table);
width = min_code_bits + 1;
prev = None;
continue;
}
if code == end {
break;
}
let entry = if usize::from(code) < table.len() {
table[usize::from(code)].clone()
} else {
let prefix = table.get(usize::from(prev?))?;
let mut entry = prefix.clone();
entry.push(*entry.first()?);
entry
};
let first = *entry.first()?;
if out.len() + entry.len() > MAX_OUTPUT {
return None;
}
out.extend_from_slice(&entry);
if let Some(prev) = prev {
let mut new = table.get(usize::from(prev))?.clone();
new.push(first);
table.push(new);
if table.len() >= 1 << width && width < MAX_CODE_BITS {
width += 1;
}
}
prev = Some(code);
}
Some((out, restarts))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips() {
let cases: Vec<Vec<u8>> = vec![
vec![],
vec![3],
vec![0; 500],
(0..4000u32).map(|i| (i % 16) as u8).collect(),
(0..9000u32).map(|i| ((i * i) % 16) as u8).collect(),
];
for symbols in cases {
let encoded = compress(&symbols, 4);
let decoded = decompress(&encoded, 4);
assert_eq!(decoded.as_ref(), Some(&symbols), "{} bytes", symbols.len());
}
let text = b"the tide is out, the tide is out, the tide is out".to_vec();
assert_eq!(decompress(&compress(&text, 8), 8), Some(text));
}
#[test]
fn survives_a_full_dictionary() {
let mut rng = crate::sim::Pcg32::new(0x1ADE_C0DE, 0x9971);
let symbols: Vec<u8> = (0..3 * 144 * 112)
.map(|_| (rng.next_u32() % 16) as u8)
.collect();
let encoded = compress(&symbols, 4);
let (decoded, restarts) = decode(&encoded, 4).expect("our own stream decodes");
assert!(
restarts >= 2,
"expected the dictionary to fill and reset; it reset {restarts} times"
);
assert_eq!(decoded, symbols, "the two sides restarted out of step");
}
#[test]
fn round_trips_at_every_code_width() {
let mut rng = crate::sim::Pcg32::new(7, 3);
for bits in 1..=8u8 {
let alphabet = 1u32 << bits;
let symbols: Vec<u8> = (0..5000)
.map(|_| (rng.next_u32() % alphabet) as u8)
.collect();
let min_code_bits = bits.max(2);
let encoded = compress(&symbols, min_code_bits);
let decoded = decompress(&encoded, min_code_bits);
assert_eq!(decoded.as_ref(), Some(&symbols), "{bits}-bit alphabet");
}
}
#[test]
fn nonsense_is_refused_rather_than_followed() {
assert_eq!(decompress(&[0xFF, 0xFF, 0xFF, 0xFF], 8), None);
for seed in 0..400u32 {
let junk: Vec<u8> = (0..40u32)
.map(|i| (seed.wrapping_mul(2_654_435_761).wrapping_add(i * 97) >> 5) as u8)
.collect();
let _ = decompress(&junk, 8);
}
}
}