use crate::lossless::prelude::*;
const NONE: usize = usize::MAX;
struct Node {
count: u64,
left: usize,
right: usize,
symbol: usize,
}
fn two_minima(active: &[usize], pool: &[Node]) -> (usize, usize) {
let cost = |idx: usize| (pool[idx].count, idx);
let (mut min1, mut min2) = if cost(active[1]) < cost(active[0]) {
(1usize, 0usize)
} else {
(0usize, 1usize)
};
for (pos, &idx) in active.iter().enumerate().skip(2) {
let key = cost(idx);
if key < cost(active[min1]) {
min2 = min1;
min1 = pos;
} else if key < cost(active[min2]) {
min2 = pos;
}
}
(min1, min2)
}
fn assign_depths(pool: &[Node], root: usize, alphabet_size: usize) -> Vec<u32> {
let mut lengths = vec![0u32; alphabet_size];
let mut stack = vec![(root, 0u32)];
while let Some((idx, depth)) = stack.pop() {
let node = &pool[idx];
if node.symbol == NONE {
stack.push((node.left, depth + 1));
stack.push((node.right, depth + 1));
} else {
lengths[node.symbol] = depth;
}
}
lengths
}
fn huffman_code_lengths(histogram: &[u32], count_min: u64) -> Vec<u32> {
let mut pool: Vec<Node> = Vec::new();
for (symbol, &freq) in histogram.iter().enumerate() {
if freq != 0 {
pool.push(Node {
count: u64::from(freq).max(count_min),
left: NONE,
right: NONE,
symbol,
});
}
}
debug_assert!(pool.len() >= 2, "caller must handle the 0/1-symbol cases");
let mut active: Vec<usize> = (0..pool.len()).collect();
while active.len() > 1 {
let (pos_a, pos_b) = two_minima(&active, &pool);
let (a, b) = (active[pos_a], active[pos_b]);
let parent = pool.len();
pool.push(Node {
count: pool[a].count + pool[b].count,
left: a,
right: b,
symbol: NONE,
});
let (hi, lo) = (pos_a.max(pos_b), pos_a.min(pos_b));
active.swap_remove(hi);
active.swap_remove(lo);
active.push(parent);
}
assign_depths(&pool, active[0], histogram.len())
}
#[must_use]
pub(crate) fn build_code_lengths(histogram: &[u32], limit: u32) -> Vec<u32> {
let num_used = histogram.iter().filter(|&&freq| freq != 0).count();
let mut lengths = vec![0u32; histogram.len()];
match num_used {
0 => return lengths,
1 => {
if let Some(idx) = histogram.iter().position(|&freq| freq != 0) {
lengths[idx] = 1;
}
return lengths;
},
_ => {},
}
let mut count_min = 1u64;
loop {
let candidate = huffman_code_lengths(histogram, count_min);
if candidate.iter().copied().max().unwrap_or(0) <= limit {
return candidate;
}
count_min <<= 1;
}
}
#[cfg(test)]
mod tests {
use super::build_code_lengths;
use crate::lossless::constants::HUFFMAN_TABLE_BITS;
use crate::lossless::huffman::decode::HuffmanTable;
fn max_length(lengths: &[u32]) -> u32 {
lengths.iter().copied().max().unwrap_or(0)
}
fn assert_decoder_accepts(lengths: &[u32]) {
assert!(
HuffmanTable::build(lengths, HUFFMAN_TABLE_BITS).is_some(),
"decoder must accept lengths {lengths:?}"
);
}
fn assert_kraft_when_multi(lengths: &[u32]) {
let used: Vec<u32> = lengths.iter().copied().filter(|&l| l != 0).collect();
if used.len() <= 1 {
return;
}
let max_len = used.iter().copied().max().unwrap();
let sum: u64 = used.iter().map(|&l| 1u64 << (max_len - l)).sum();
assert_eq!(
sum,
1u64 << max_len,
"Kraft equality must hold for {lengths:?}"
);
}
#[test]
fn empty_alphabet_is_all_zero() {
assert_eq!(build_code_lengths(&[0, 0, 0], 15), vec![0, 0, 0]);
assert!(build_code_lengths(&[], 15).is_empty());
}
#[test]
fn single_used_symbol_gets_length_one() {
let mut hist = vec![0u32; 10];
hist[3] = 42;
let lengths = build_code_lengths(&hist, 15);
let mut expected = vec![0u32; 10];
expected[3] = 1;
assert_eq!(lengths, expected);
assert_decoder_accepts(&lengths);
}
#[test]
fn two_used_symbols_each_get_length_one() {
let lengths = build_code_lengths(&[5, 9], 15);
assert_eq!(lengths, vec![1, 1]);
assert_kraft_when_multi(&lengths);
assert_decoder_accepts(&lengths);
}
#[test]
fn several_histograms_produce_decodable_codes() {
let cases: [&[u32]; 5] = [
&[1, 1, 1, 1],
&[10, 1, 1, 1, 1],
&[100, 50, 25, 12, 6, 3, 1],
&[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
&[0, 7, 0, 0, 2, 5, 0, 9, 1, 0],
];
for hist in cases {
let lengths = build_code_lengths(hist, 15);
assert_eq!(lengths.len(), hist.len());
for (i, &l) in lengths.iter().enumerate() {
assert_eq!(l == 0, hist[i] == 0, "used-ness must match for {hist:?}");
}
assert!(max_length(&lengths) <= 15, "must respect the 15-bit limit");
assert_kraft_when_multi(&lengths);
assert_decoder_accepts(&lengths);
}
}
#[test]
fn fibonacci_weights_are_depth_limited() {
let mut hist = Vec::new();
let (mut a, mut b) = (1u32, 1u32);
for _ in 0..40 {
hist.push(a);
let next = a.saturating_add(b);
a = b;
b = next;
}
let lengths = build_code_lengths(&hist, 15);
assert!(max_length(&lengths) <= 15, "count_min must cap depth at 15");
assert_kraft_when_multi(&lengths);
assert_decoder_accepts(&lengths);
}
#[test]
fn skewed_nineteen_entries_respect_limit_seven() {
let hist: [u32; 19] = [
1000, 500, 250, 120, 60, 30, 15, 8, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
let lengths = build_code_lengths(&hist, 7);
assert!(
max_length(&lengths) <= 7,
"must respect the 7-bit meta limit"
);
assert_kraft_when_multi(&lengths);
assert_decoder_accepts(&lengths);
}
}
#[cfg(test)]
mod proptests {
use super::build_code_lengths;
use crate::lossless::constants::HUFFMAN_TABLE_BITS;
use crate::lossless::huffman::decode::HuffmanTable;
use proptest::prelude::*;
proptest! {
#[test]
fn produces_valid_length_limited_codes(
freqs in proptest::collection::vec(0u32..=4096, 1..=280)
) {
let lengths = build_code_lengths(&freqs, 15);
prop_assert_eq!(lengths.len(), freqs.len());
let mut used = 0usize;
for (i, &l) in lengths.iter().enumerate() {
if freqs[i] == 0 {
prop_assert_eq!(l, 0, "unused symbol must have length 0");
} else {
prop_assert!((1..=15).contains(&l), "used length must be in 1..=15");
used += 1;
}
}
if used > 1 {
let max_len = lengths.iter().copied().max().unwrap_or(0);
let sum: u64 = lengths
.iter()
.filter(|&&l| l != 0)
.map(|&l| 1u64 << (max_len - l))
.sum();
prop_assert_eq!(sum, 1u64 << max_len, "Kraft equality must hold");
}
if used >= 1 {
prop_assert!(
HuffmanTable::build(&lengths, HUFFMAN_TABLE_BITS).is_some(),
"decoder must accept the generated lengths"
);
}
}
}
}