-
-
Notifications
You must be signed in to change notification settings - Fork 359
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,130 @@ | ||||||
import java.util.* | ||||||
|
||||||
// node type | ||||||
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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
populateCodebook(tree, "", codebook) | ||||||
} | ||||||
|
||||||
// recursively populate a codebook with encodings from a node | ||||||
fun populateCodebook(node: Node, code: String, codebook: MutableMap<Char, String>) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer direct assignment:
Suggested change
|
||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For here and above, prefer template syntax rather than concatenation:
Suggested change
|
||||||
// create a separate codebook and print it | ||||||
val codeBook = CodeBook(huffmanTree.root) | ||||||
println("Codebook:") | ||||||
codeBook.print() | ||||||
println("Reverse codebook:") | ||||||
codeBook.printReverse() | ||||||
} | ||||||
|
There was a problem hiding this comment.
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.