use std::cmp::Reverse;
use std::collections::BinaryHeap;
pub(crate) fn complete_lengths_for_frequencies(frequencies: &[usize], max_bits: u8) -> Vec<u8> {
let mut lengths = lengths_for_frequencies(frequencies, max_bits);
if !is_complete_code(&lengths) {
assign_flat_complete_code(&mut lengths);
}
lengths
}
fn is_complete_code(lengths: &[u8]) -> bool {
let max_len = lengths.iter().copied().max().unwrap_or(0);
if max_len == 0 {
return true; }
let mut sum: u64 = 0;
for &len in lengths {
if len != 0 {
sum += 1u64 << (max_len - len);
}
}
sum == (1u64 << max_len)
}
pub(crate) fn assign_flat_complete_code(lengths: &mut [u8]) {
let used: Vec<usize> = lengths
.iter()
.enumerate()
.filter(|(_, &len)| len != 0)
.map(|(symbol, _)| symbol)
.collect();
let n = used.len();
if n == 0 {
return;
}
for len in lengths.iter_mut() {
*len = 0;
}
if n == 1 {
lengths[used[0]] = 1;
let phantom = if used[0] == 0 { 1 } else { 0 };
if phantom < lengths.len() {
lengths[phantom] = 1;
}
return;
}
let k = (usize::BITS - (n - 1).leading_zeros()) as u8; let cap = 1usize << k;
let short_count = cap - n; for (i, &symbol) in used.iter().enumerate() {
lengths[symbol] = if i < short_count { k - 1 } else { k };
}
}
pub(crate) fn lengths_for_frequencies(frequencies: &[usize], max_bits: u8) -> Vec<u8> {
let used_count = frequencies
.iter()
.filter(|&&frequency| frequency != 0)
.count();
if used_count <= 1 {
return uniform_lengths_for_frequencies(frequencies);
}
let mut lengths = vec![0u8; frequencies.len()];
let mut heap = BinaryHeap::new();
let mut order = 0usize;
for (symbol, &frequency) in frequencies.iter().enumerate() {
if frequency == 0 {
continue;
}
heap.push(Reverse((frequency, order, vec![symbol])));
order += 1;
}
while heap.len() > 1 {
let Reverse((left_frequency, _, mut left_symbols)) =
heap.pop().expect("frequency heap has a left node");
let Reverse((right_frequency, _, mut right_symbols)) =
heap.pop().expect("frequency heap has a right node");
for &symbol in left_symbols.iter().chain(right_symbols.iter()) {
lengths[symbol] += 1;
}
left_symbols.append(&mut right_symbols);
heap.push(Reverse((
left_frequency.saturating_add(right_frequency),
order,
left_symbols,
)));
order += 1;
}
if lengths.iter().any(|&length| length > max_bits) {
limit_code_lengths(&mut lengths, frequencies, max_bits);
}
lengths
}
fn limit_code_lengths(lengths: &mut [u8], frequencies: &[usize], max_bits: u8) {
let used = lengths.iter().filter(|&&length| length != 0).count();
if max_bits == 0 || max_bits >= usize::BITS as u8 || used > (1usize << max_bits) {
lengths.copy_from_slice(&uniform_lengths_for_frequencies(frequencies));
return;
}
for length in lengths.iter_mut() {
if *length > max_bits {
*length = max_bits;
}
}
let target: u64 = 1 << max_bits;
let mut sum: u64 = lengths
.iter()
.filter(|&&length| length != 0)
.map(|&length| 1u64 << (max_bits - length))
.sum();
while sum > target {
let Some(symbol) = (0..lengths.len())
.filter(|&symbol| lengths[symbol] != 0 && lengths[symbol] < max_bits)
.min_by_key(|&symbol| (frequencies[symbol], std::cmp::Reverse(lengths[symbol])))
else {
break;
};
sum -= 1u64 << (max_bits - lengths[symbol] - 1);
lengths[symbol] += 1;
}
while let Some(symbol) = (0..lengths.len())
.filter(|&symbol| lengths[symbol] > 1)
.filter(|&symbol| sum + (1u64 << (max_bits - lengths[symbol])) <= target)
.max_by_key(|&symbol| (frequencies[symbol], lengths[symbol]))
{
sum += 1u64 << (max_bits - lengths[symbol]);
lengths[symbol] -= 1;
}
}
pub(crate) fn lengths_for_frequency_array<const N: usize>(
frequencies: &[usize; N],
max_bits: u8,
) -> [u8; N] {
let mut lengths = [0u8; N];
lengths.copy_from_slice(&lengths_for_frequencies(frequencies, max_bits));
lengths
}
pub(crate) fn uniform_lengths_for_frequencies(frequencies: &[usize]) -> Vec<u8> {
let used_count = frequencies
.iter()
.filter(|&&frequency| frequency != 0)
.count();
let uniform_length = bits_for_symbol_count(used_count);
frequencies
.iter()
.map(|&frequency| if frequency == 0 { 0 } else { uniform_length })
.collect()
}
pub(crate) fn bits_for_symbol_count(count: usize) -> u8 {
match count {
0 | 1 => 1,
_ => usize::BITS as u8 - (count - 1).leading_zeros() as u8,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weighted_lengths_favour_common_symbols() {
let frequencies = [1, 1, 16, 1];
let lengths = lengths_for_frequencies(&frequencies, 15);
assert!(lengths[2] < lengths[0]);
assert!(lengths.iter().all(|&length| length <= 15));
}
#[test]
fn lengths_too_deep_to_limit_fall_back_to_uniform_lengths() {
let frequencies = (1..=1024).collect::<Vec<_>>();
let lengths = lengths_for_frequencies(&frequencies, 1);
assert!(lengths.iter().all(|&length| length == 10));
}
#[test]
fn deep_codes_are_limited_rather_than_flattened() {
let mut frequencies = vec![1usize, 1];
while frequencies.len() < 40 {
let next = frequencies[frequencies.len() - 1] + frequencies[frequencies.len() - 2];
frequencies.push(next);
}
let lengths = lengths_for_frequencies(&frequencies, 15);
assert!(lengths.iter().all(|&length| length <= 15), "{lengths:?}");
assert!(is_complete_code(&lengths), "{lengths:?}");
let flat: usize = frequencies.iter().sum::<usize>() * 6;
let limited: usize = frequencies
.iter()
.zip(&lengths)
.map(|(&frequency, &length)| frequency * usize::from(length))
.sum();
assert!(limited < flat, "limited {limited} flat {flat}");
}
#[test]
fn limited_codes_stay_decodable_for_every_shape_of_input() {
let mut seed = 0x2545_f491_4f6c_dd1du64;
let mut next = move || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
for symbols in [2usize, 3, 17, 64, 255, 299] {
for skew in 0..8 {
let frequencies: Vec<usize> = (0..symbols)
.map(|_| match skew {
0 => 1,
s => 1 + (next() % (1u64 << (s * 8).min(60))) as usize,
})
.collect();
let lengths = lengths_for_frequencies(&frequencies, 15);
assert_eq!(lengths.len(), symbols);
assert!(
lengths.iter().all(|&length| length <= 15),
"over the limit: {symbols} symbols, skew {skew}"
);
let deepest = lengths.iter().copied().max().unwrap_or(0);
if deepest > 0 {
let kraft: u64 = lengths
.iter()
.filter(|&&length| length != 0)
.map(|&length| 1u64 << (deepest - length))
.sum();
assert!(
kraft <= 1u64 << deepest,
"not a prefix code: {symbols} symbols, skew {skew}"
);
}
}
}
}
fn kraft_sum_is_one(lengths: &[u8]) -> bool {
let max_len = lengths.iter().copied().max().unwrap_or(0);
if max_len == 0 {
return lengths.iter().all(|&len| len == 0);
}
let sum: u64 = lengths
.iter()
.filter(|&&len| len != 0)
.map(|&len| 1u64 << (max_len - len))
.sum();
sum == (1u64 << max_len)
}
#[test]
fn single_symbol_table_is_completed_with_a_phantom_code() {
for used in [0usize, 1, 7, 40] {
let mut frequencies = vec![0usize; 44];
frequencies[used] = 123;
let lengths = complete_lengths_for_frequencies(&frequencies, 15);
assert_eq!(lengths[used], 1, "used symbol {used} keeps a length-1 code");
assert_eq!(
lengths.iter().filter(|&&len| len != 0).count(),
2,
"exactly one phantom code was added for used symbol {used}"
);
assert!(
kraft_sum_is_one(&lengths),
"used symbol {used} yields a complete code"
);
}
}
#[test]
fn empty_table_stays_empty() {
let lengths = complete_lengths_for_frequencies(&[0usize; 16], 15);
assert!(lengths.iter().all(|&len| len == 0));
}
#[test]
fn completed_codes_are_always_complete_for_any_symbol_count() {
for used_count in 1..=64usize {
let mut frequencies = vec![0usize; 306];
for (i, freq) in frequencies.iter_mut().take(used_count).enumerate() {
*freq = 1 + i; }
let lengths = complete_lengths_for_frequencies(&frequencies, 15);
assert!(
kraft_sum_is_one(&lengths),
"code for {used_count} symbols must be complete"
);
assert!(lengths.iter().all(|&len| len <= 15));
}
}
#[test]
fn multi_symbol_huffman_code_is_left_optimal() {
let frequencies = [100usize, 1, 1, 1, 1];
let optimal = lengths_for_frequencies(&frequencies, 15);
let completed = complete_lengths_for_frequencies(&frequencies, 15);
assert_eq!(optimal, completed);
assert!(kraft_sum_is_one(&completed));
}
}