Skip to content

Huffman encoding Kotlin implementation #640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions contents/huffman_encoding/code/kotlin/huffman.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import java.util.*

// node type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't really add anything.

sealed class Node(val frequency: Int): Comparable<Node> {
// we can sort nodes by frequency
override fun compareTo(other: Node) = frequency - other.frequency
}
class Leaf(freq: Int, val letter: Char): Node(freq)
class Branch(freq: Int, val left: Node, val right: Node): Node(freq)

class HuffmanTree (val textSample: String) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class HuffmanTree (val textSample: String) {
class HuffmanTree (textSample: String) {

val root: Node
val codeBook: CodeBook

init {
// calculate frequencies
val frequencyMap = HashMap<Char, Int>()
for (char in textSample) {
val newFrequency = (frequencyMap.get(char) ?: 0) + 1
frequencyMap.put(char, newFrequency)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace both lines with

frequencyMap[char] = frequencyMap.getOrDefault(char, 0)

}
// populate the queue with leaves
val priorityQueue = PriorityQueue<Node>()
for (m in frequencyMap.entries) {
priorityQueue.add(Leaf(m.value, m.key))
}
// join leaves
while (priorityQueue.size > 1) {
val left = priorityQueue.remove()
val right = priorityQueue.remove()
priorityQueue.add(Branch(left.frequency + right.frequency, left, right))
}
// the root node is the last remaining leaf
root = priorityQueue.remove()
// initialize the code book
codeBook = CodeBook(root)
}

// encode a string using the tree
fun encode(source: String): String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun encode(source: String): String {
fun encode(source: String) = source.map { codeBook.encode(it) }.joinToString(separator = "")

replaces the whole function.

val encoder = StringBuilder()
for (char in source) {
encoder.append(codeBook.encode(char))
}
return encoder.toString()
}

// decode an encoded string
fun decode(encoded: String) = buildString {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't work it out for you, but some of this can be re-written using functional idioms.

var currentSequence = StringBuilder()
for (k in encoded) {
currentSequence.append(k)
val decoded = codeBook.decode(currentSequence.toString())
if(decoded != null) {
append(decoded)
currentSequence = StringBuilder()
}
}
}
}

// a codebook is a mapping between a huffman code and a character, and the other way around
class CodeBook(tree: Node) {
val codebook: Map<Char, String>

init {
codebook = HashMap<Char, String>()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
codebook = HashMap<Char, String>()
codebook = HashMap()

populateCodebook(tree, "", codebook)
}

// recursively populate a codebook with encodings from a node
fun populateCodebook(node: Node, code: String, codebook: MutableMap<Char, String>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be private.

when(node) {
// leaf node, add current children
is Leaf -> codebook.put(node.letter, code.toString())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer direct assignment:

Suggested change
is Leaf -> codebook.put(node.letter, code.toString())
is Leaf -> codebook[node.letter] = code

is Branch -> {
// populate using left and right children
populateCodebook(node.left, code + "0", codebook)
populateCodebook(node.right, code + "1", codebook)
}
}
}

// encode a letter
fun encode(letter: Char) = codebook[letter]

// the reverse codebook is just the original one with keys as values and values as keys
val reverseCodebook: HashMap<String, Char> by lazy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be private.

val reversed = HashMap<String, Char>()
for (m in codebook.entries) {
reversed.put(m.value, m.key)
}
reversed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace the whole block with

codebook.entries.associateBy({ it.value }) { it.key }

}
// decode a sequence of bits by looking them up in the reverse codebook
fun decode(encoded: String) = reverseCodebook[encoded]

// print the codebook to stdout
fun print() {
for (m in codebook.entries) {
println(m.key + ": " + m.value)
}
println()
}

// print the reverse codebook to stdout
fun printReverse() {
for (m in reverseCodebook.entries) {
println(m.key + ": " + m.value)
}
println()
}
}

fun main(args: Array<String>) {
val sourceText = "bibbity_bobbity"
// create huffman tree
val huffmanTree = HuffmanTree(sourceText)
// encode the text
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the above comment aren't very useful.

val encoded = huffmanTree.encode(sourceText)
println("Encoded String: " + encoded)
println("Decoded String: " + huffmanTree.decode(encoded))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For here and above, prefer template syntax rather than concatenation:

Suggested change
println("Decoded String: " + huffmanTree.decode(encoded))
println("Decoded String: ${huffmanTree.decode(encoded)}")

// create a separate codebook and print it
val codeBook = CodeBook(huffmanTree.root)
println("Codebook:")
codeBook.print()
println("Reverse codebook:")
codeBook.printReverse()
}

2 changes: 2 additions & 0 deletions contents/huffman_encoding/huffman_encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Whether you use a stack or straight-up recursion also depends on the language, b
[import, lang:"asm-x64"](code/asm-x64/huffman.s)
{% sample lang="scala" %}
[import, lang:"scala"](code/scala/huffman_encoding.scala)
{% sample lang="kotlin" %}
[import, lang:"kotlin"](code/kotlin/huffman.kt)
{% endmethod %}

<script>
Expand Down