290 lines
9.4 KiB
Kotlin
290 lines
9.4 KiB
Kotlin
/*
|
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package net.sergeych.lynon
|
|
|
|
import net.sergeych.collections.SortedList
|
|
|
|
|
|
/**
|
|
* Generic huffman encoding implementation using bits input/output and abstract [Alphabet].
|
|
*/
|
|
object Huffman {
|
|
|
|
/**
|
|
* Alphabet interface: source can be variable bit size codes, not just bytes,
|
|
* so the Huffman encoding is not limited to bytes. It works with any alphabet
|
|
* using its _ordinals_; encoding between source symbols and ordinals are
|
|
* performed by the alphabet. See [byteAlphabet] for example.
|
|
*/
|
|
interface Alphabet<T> {
|
|
val maxOrdinal: Int
|
|
|
|
/**
|
|
* Write correct symbol for the [ordinal] to the [bout]. This is
|
|
* the inverse of [ordinalOf] but as [T] could be variable bit size,
|
|
* we provide output bit stream.
|
|
*/
|
|
fun decodeOrdinalTo(bout: BitOutput, ordinal: Int)
|
|
|
|
/**
|
|
* Find the ordinal of the source symbol
|
|
*/
|
|
fun ordinalOf(value: T): Int
|
|
|
|
operator fun get(ordinal: Int): T
|
|
}
|
|
|
|
/**
|
|
* Alphabet for unsigned bytes, allows to encode bytes easily
|
|
*/
|
|
val byteAlphabet = object : Alphabet<UByte> {
|
|
override val maxOrdinal: Int
|
|
get() = 256
|
|
|
|
override fun decodeOrdinalTo(bout: BitOutput, ordinal: Int) {
|
|
bout.putBits(ordinal, 8)
|
|
}
|
|
|
|
override fun ordinalOf(value: UByte): Int = value.toInt()
|
|
|
|
override operator fun get(ordinal: Int): UByte = ordinal.toUByte()
|
|
}
|
|
|
|
sealed class Node(val freq: Int) : Comparable<Node> {
|
|
override fun compareTo(other: Node): Int {
|
|
return freq.compareTo(other.freq)
|
|
}
|
|
|
|
abstract fun decodeOrdinal(bin: BitInput): Int?
|
|
|
|
class Leaf(val ordinal: Int, freq: Int) : Node(freq) {
|
|
override fun toString(): String {
|
|
return "[$ordinal:$freq]"
|
|
}
|
|
|
|
override fun decodeOrdinal(bin: BitInput): Int {
|
|
return ordinal//.also { println(": ${Char(value)}") }
|
|
}
|
|
}
|
|
|
|
class Internal(val left: Node, val right: Node) : Node(left.freq + right.freq) {
|
|
override fun toString(): String {
|
|
return "[${left.freq}<- :<$freq>: ->${right.freq}]"
|
|
}
|
|
|
|
override fun decodeOrdinal(bin: BitInput): Int? {
|
|
return when (bin.getBitOrNull().also { print("$it") }) {
|
|
1 -> left.decodeOrdinal(bin)
|
|
0 -> right.decodeOrdinal(bin)
|
|
else -> null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
data class Code(val ordinal: Int, val bits: TinyBits) {
|
|
|
|
val size by bits::size
|
|
|
|
override fun toString(): String {
|
|
return "[$ordinal:$size:$bits]"
|
|
}
|
|
|
|
}
|
|
|
|
private fun generateCanonicCodes(tree: Node, alphabet: Alphabet<*>): List<Code?> {
|
|
val codes = MutableList<Code?>(alphabet.maxOrdinal) { null }
|
|
|
|
fun traverse(node: Node, code: TinyBits) {
|
|
when (node) {
|
|
is Node.Leaf ->
|
|
codes[node.ordinal] = (Code(node.ordinal, code))
|
|
|
|
is Node.Internal -> {
|
|
traverse(node.left, code.insertBit(1))
|
|
traverse(node.right, code.insertBit(0))
|
|
}
|
|
}
|
|
}
|
|
traverse(tree, TinyBits())
|
|
|
|
return makeCanonical(codes, alphabet)
|
|
}
|
|
|
|
private fun makeCanonical(source: List<Code?>,alphabet: Alphabet<*>): List<Code?> {
|
|
val sorted = source.filterNotNull().sortedWith(canonicComparator)
|
|
|
|
val canonical = MutableList<Code?>(alphabet.maxOrdinal) { null }
|
|
|
|
val first = sorted[0]
|
|
val prevValue = first.copy(bits = TinyBits(0UL, first.bits.size))
|
|
canonical[first.ordinal] = prevValue
|
|
var prev = prevValue.bits
|
|
|
|
for (i in 1..<sorted.size) {
|
|
var bits = TinyBits(prev.value + 1U, prev.size)
|
|
val code = sorted[i]
|
|
while (code.bits.size > bits.size) {
|
|
bits = bits.insertBit(0)
|
|
}
|
|
canonical[code.ordinal] = code.copy(bits = bits)//.also { println("$it") }
|
|
prev = bits
|
|
}
|
|
return canonical
|
|
}
|
|
|
|
private val canonicComparator = { a: Code, b: Code ->
|
|
if (a.bits.size == b.bits.size) {
|
|
a.ordinal.compareTo(b.ordinal)
|
|
} else {
|
|
a.bits.size.compareTo(b.bits.size)
|
|
}
|
|
}
|
|
|
|
private fun buildTree(data: Iterable<Int>,alphabet: Alphabet<*>): Node {
|
|
val frequencies = buildFrequencies(alphabet, data)
|
|
return buildTree(frequencies)
|
|
}
|
|
|
|
private fun buildTree(frequencies: Array<Int>): Node {
|
|
// println(data.toDump())
|
|
|
|
val list: SortedList<Node> = SortedList(*frequencies.mapIndexed { index, frequency -> Node.Leaf(index, frequency) }.filter { it.freq > 0 }
|
|
.toTypedArray())
|
|
|
|
// build the tree
|
|
while (list.size > 1) {
|
|
val left = list.removeAt(0)
|
|
val right = list.removeAt(0)
|
|
list.add(Node.Internal(left, right))
|
|
}
|
|
return list[0]
|
|
}
|
|
|
|
private fun buildFrequencies(
|
|
alphabet: Alphabet<*>,
|
|
data: Iterable<Int>
|
|
): Array<Int> {
|
|
val maxOrdinal = alphabet.maxOrdinal
|
|
val frequencies = Array(maxOrdinal) { 0 }
|
|
data.forEach { frequencies[it]++ }
|
|
return frequencies
|
|
}
|
|
|
|
fun decompressUsingCodes(bin: BitInput, codes: List<Code?>, alphabet: Alphabet<*>): BitArray {
|
|
val result = MemoryBitOutput()
|
|
val table = codes.filterNotNull().associateBy { it.bits }
|
|
|
|
outer@ while (true) {
|
|
var input = TinyBits()
|
|
while (true) {
|
|
bin.getBitOrNull()?.let { input = input.insertBit(it) }
|
|
?: break@outer
|
|
val data = table[input]
|
|
if (data != null) {
|
|
// println("Code found: ${data.bits} -> [${data.symbol.toChar()}]")
|
|
alphabet.decodeOrdinalTo(result,data.ordinal)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return result.toBitArray()
|
|
}
|
|
|
|
private fun serializeCanonicCodes(bout: BitOutput, codes: List<Code?>) {
|
|
var minSize: Int? = null
|
|
var maxSize: Int? = null
|
|
for (i in 1..<codes.size) {
|
|
val s = codes[i]?.size?.toInt() ?: continue
|
|
if (minSize == null || s < minSize) minSize = s
|
|
if (maxSize == null || s > maxSize) maxSize = s
|
|
}
|
|
val size = maxSize!! - minSize!! + 1
|
|
val sizeInBits = sizeInBits(size)
|
|
bout.packUnsigned(minSize.toULong())
|
|
bout.packUnsigned(sizeInBits.toULong())
|
|
for (c in codes) {
|
|
if (c != null)
|
|
bout.putBits(c.bits.size.toInt() - minSize + 1, sizeInBits)
|
|
else
|
|
bout.putBits(0, sizeInBits)
|
|
}
|
|
}
|
|
|
|
fun deserializeCanonicCodes(bin: BitInput, alphabet: Alphabet<*>): List<Code?> {
|
|
val minSize = bin.unpackUnsigned().toInt()
|
|
val sizeInBits = bin.unpackUnsigned().toInt()
|
|
val sorted = mutableListOf<Code>().also { codes ->
|
|
for (i in 0..<alphabet.maxOrdinal) {
|
|
val s = bin.getBits(sizeInBits).toInt()
|
|
if (s > 0) {
|
|
codes.add(Code(i, TinyBits(0U, s - 1 + minSize)))
|
|
}
|
|
}
|
|
}.sortedWith(canonicComparator)
|
|
|
|
val result = MutableList<Code?>(alphabet.maxOrdinal) { null }
|
|
var prev = sorted[0].copy(bits = TinyBits(0U, sorted[0].bits.size))
|
|
result[prev.ordinal] = prev
|
|
|
|
for (i in 1..<sorted.size) {
|
|
val code = sorted[i]
|
|
var bits = TinyBits(prev.bits.value + 1u, prev.bits.size)
|
|
while (bits.size < code.bits.size) bits = bits.insertBit(0)
|
|
result[code.ordinal] = code.copy(bits = bits).also {
|
|
prev = it
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// fun generateCanonicalCodes(frequencies: Iterable<Int>): List<Code?> {
|
|
//
|
|
// }
|
|
|
|
fun generateCanonicalCodes(frequencies: Array<Int>,alphabet: Alphabet<*>): List<Code?> =
|
|
generateCanonicCodes(buildTree(frequencies), alphabet)
|
|
|
|
fun <T>compress(plain: Iterable<T>,alphabet: Alphabet<T>): BitArray {
|
|
|
|
val source = plain.map { alphabet.ordinalOf(it) }
|
|
val root = buildTree(source,alphabet)
|
|
|
|
val codes = generateCanonicCodes(root, alphabet)
|
|
|
|
// serializa table
|
|
|
|
// test encode:
|
|
val bout = MemoryBitOutput()
|
|
serializeCanonicCodes(bout, codes)
|
|
for (i in source) {
|
|
val code = codes[i]!!
|
|
// println(">> $code")
|
|
bout.putBits(code.bits)
|
|
}
|
|
// println(bout.toBitArray().bytes.toDump())
|
|
val compressed = bout.toBitArray()
|
|
return compressed
|
|
}
|
|
|
|
fun <T>decompress(bin: BitInput,alphabet: Alphabet<T>): UByteArray {
|
|
val codes = deserializeCanonicCodes(bin, alphabet)
|
|
return decompressUsingCodes(bin, codes, alphabet).asUByteArray()
|
|
}
|
|
|
|
} |