use crate::lossless::prelude::*;
#[must_use]
pub(crate) const fn reverse_bits(v: u32, len: u32) -> u32 {
let mut out = 0u32;
let mut i = 0u32;
while i < len {
out |= ((v >> i) & 1) << (len - 1 - i);
i += 1;
}
out
}
#[must_use]
pub(crate) fn canonical_codes(lengths: &[u32]) -> Vec<u32> {
let max_len = lengths.iter().copied().max().unwrap_or(0) as usize;
let mut count = vec![0u32; max_len + 1];
for &l in lengths {
if l > 0 {
count[l as usize] += 1;
}
}
let mut next = vec![0u32; max_len + 2];
let mut code = 0u32;
for len in 1..=max_len {
code = (code + count[len - 1]) << 1;
next[len] = code;
}
let mut codes = vec![0u32; lengths.len()];
for (sym, &l) in lengths.iter().enumerate() {
if l > 0 {
codes[sym] = next[l as usize];
next[l as usize] += 1;
}
}
codes
}
#[must_use]
pub(crate) fn emit_codes(lengths: &[u32]) -> Vec<(u32, u32)> {
let used = lengths.iter().filter(|&&l| l > 0).count();
if used <= 1 {
return vec![(0, 0); lengths.len()];
}
let canon = canonical_codes(lengths);
lengths
.iter()
.zip(canon.iter())
.map(|(&len, &code)| {
if len > 0 {
(reverse_bits(code, len), len)
} else {
(0, 0)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{canonical_codes, emit_codes, reverse_bits};
use crate::lossless::bit_io::reader::BitReader;
use crate::lossless::bit_io::writer::BitWriter;
use crate::lossless::constants::HUFFMAN_TABLE_BITS;
use crate::lossless::huffman::decode::HuffmanTable;
#[test]
fn reverse_bits_known_values() {
assert_eq!(reverse_bits(0b1, 1), 0b1);
assert_eq!(reverse_bits(0b10, 2), 0b01);
assert_eq!(reverse_bits(0b110, 3), 0b011);
assert_eq!(reverse_bits(0b1011, 4), 0b1101);
assert_eq!(reverse_bits(0b1111_0010, 3), 0b010);
assert_eq!(reverse_bits(0x7, 0), 0);
}
#[test]
fn reverse_bits_is_an_involution() {
for len in 1u32..=15 {
let mask = (1u32 << len) - 1;
for v in [0u32, 1, mask, mask ^ 1, 0xA5A5_A5A5 & mask] {
assert_eq!(reverse_bits(reverse_bits(v, len), len), v);
}
}
}
#[test]
fn canonical_codes_assigns_standard_order() {
assert_eq!(canonical_codes(&[1, 2, 3, 3]), vec![0, 2, 6, 7]);
assert_eq!(canonical_codes(&[2, 0, 1, 2]), vec![2, 0, 0, 3]);
}
#[test]
fn emit_codes_single_symbol_is_all_zero_bits() {
let mut lengths = vec![0u32; 10];
lengths[5] = 1; let codes = emit_codes(&lengths);
assert!(
codes.iter().all(|&pair| pair == (0, 0)),
"a single-symbol alphabet must emit zero bits per occurrence"
);
let table = HuffmanTable::build(&lengths, HUFFMAN_TABLE_BITS)
.expect("single-symbol code builds a zero-bit leaf");
let (code, emit_len) = codes[5];
let mut w = BitWriter::new();
w.write_bits(code, emit_len); let bytes = w.into_bytes();
assert!(bytes.is_empty(), "no bits should be emitted");
let mut br = BitReader::new(&bytes);
assert_eq!(table.read_symbol(&mut br), 5);
assert!(!br.is_eos(), "reading a zero-bit leaf consumes nothing");
}
#[test]
fn emit_codes_empty_alphabet_is_all_zero_bits() {
assert_eq!(emit_codes(&[0, 0, 0]), vec![(0, 0), (0, 0), (0, 0)]);
}
#[test]
fn emit_codes_two_equal_length_symbols() {
assert_eq!(emit_codes(&[1, 1]), vec![(0, 1), (1, 1)]);
}
#[test]
fn emit_codes_matches_reversed_canonical() {
let lengths = [1u32, 2, 3, 3];
let canon = canonical_codes(&lengths);
let emitted = emit_codes(&lengths);
for (sym, &len) in lengths.iter().enumerate() {
assert_eq!(emitted[sym], (reverse_bits(canon[sym], len), len));
}
}
fn assert_round_trips(lengths: &[u32]) {
let table = HuffmanTable::build(lengths, HUFFMAN_TABLE_BITS)
.expect("test length vectors are valid prefix codes");
let codes = emit_codes(lengths);
for (sym, &len) in lengths.iter().enumerate() {
if len == 0 {
continue;
}
let (code, emit_len) = codes[sym];
let mut w = BitWriter::new();
w.write_bits(code, emit_len);
let bytes = w.into_bytes();
let mut br = BitReader::new(&bytes);
assert_eq!(
table.read_symbol(&mut br) as usize,
sym,
"symbol {sym} (len {len}) must round-trip"
);
}
}
#[test]
fn round_trips_representative_codes() {
assert_round_trips(&[1, 1]);
assert_round_trips(&[1, 2, 2]);
assert_round_trips(&[1, 2, 3, 3]);
assert_round_trips(&[2, 2, 2, 2]);
assert_round_trips(&[1, 2, 4, 4, 4, 4]);
assert_round_trips(&[3, 3, 3, 3, 3, 3, 3, 3]);
assert_round_trips(&[0, 1, 0, 2, 0, 2]);
assert_round_trips(&[0, 0, 1, 0]);
}
#[test]
fn canonical_codes_skips_unused_symbols() {
assert_eq!(canonical_codes(&[1, 0, 1, 0]), vec![0, 0, 1, 0]);
}
}
#[cfg(test)]
mod proptests {
use super::{emit_codes, reverse_bits};
use crate::lossless::bit_io::reader::BitReader;
use crate::lossless::bit_io::writer::BitWriter;
use crate::lossless::constants::HUFFMAN_TABLE_BITS;
use crate::lossless::huffman::decode::HuffmanTable;
use proptest::prelude::*;
fn build_complete_lengths(n: usize, seed: u64) -> Vec<u32> {
let mut state = seed ^ 0x9e37_79b9_7f4a_7c15;
let mut depths = vec![1u32, 1u32];
while depths.len() < n {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let candidates: Vec<usize> = (0..depths.len()).filter(|&i| depths[i] < 15).collect();
let idx = usize::try_from(state % candidates.len() as u64)
.expect("modulus is < candidates.len(), which fits usize");
let pick = candidates[idx];
depths[pick] += 1;
depths.push(depths[pick]);
}
depths
}
proptest! {
#[test]
fn reverse_bits_involution_over_len(len in 1u32..=15, raw in any::<u32>()) {
let mask = (1u32 << len) - 1;
let v = raw & mask;
prop_assert_eq!(reverse_bits(reverse_bits(v, len), len), v);
prop_assert_eq!(reverse_bits(v, len) & !mask, 0);
}
#[test]
fn canonical_codes_round_trip_through_decoder(
n in 2usize..=64,
seed in any::<u64>(),
) {
let lengths = build_complete_lengths(n, seed);
let table = HuffmanTable::build(&lengths, HUFFMAN_TABLE_BITS)
.expect("a complete code always builds");
let codes = emit_codes(&lengths);
for (sym, &len) in lengths.iter().enumerate() {
let (code, emit_len) = codes[sym];
prop_assert_eq!(emit_len, len);
let mut w = BitWriter::new();
w.write_bits(code, emit_len);
let bytes = w.into_bytes();
let mut br = BitReader::new(&bytes);
prop_assert_eq!(table.read_symbol(&mut br) as usize, sym);
}
}
}
}