Last active
July 29, 2017 17:38
-
-
Save jcttrll/8508cb22010a241ebab3f0b934d980a9 to your computer and use it in GitHub Desktop.
Simple Huffman coding example for character strings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayDeque; | |
import java.util.ArrayList; | |
import java.util.Deque; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.PriorityQueue; | |
import static java.lang.Math.min; | |
import static java.util.stream.Collectors.counting; | |
import static java.util.stream.Collectors.groupingBy; | |
import static java.util.stream.Collectors.toCollection; | |
import static java.util.stream.Collectors.toMap; | |
public class Huffman { | |
public static void main (String[] args) { | |
if (args.length != 1) { | |
System.err.println("Usage: huffman <characters>"); | |
System.exit(1); | |
} | |
huffmanCodes(args[0]).entrySet().forEach( | |
h -> System.out.println(h.getKey() + ": " + h.getValue()) | |
); | |
} | |
private static Map<Character, String> huffmanCodes(String characters) { | |
Map<Character, Long> frequencies = | |
characters.chars() | |
.mapToObj(c -> Character.valueOf((char) c)) | |
.collect(groupingBy(x -> x, counting())); | |
int discreteCharacterCount = frequencies.size(); | |
PriorityQueue<Node> nodes = | |
frequencies.entrySet().stream() | |
.map(e -> new Node(e.getKey(), e.getValue())) | |
.collect(toCollection(() -> new PriorityQueue<Node>(discreteCharacterCount))); | |
while (nodes.size() > 1) { | |
nodes.add(new Branch(nodes.remove(), nodes.remove())); | |
} | |
// Output tree for debugging, if desired | |
//System.out.println(nodes.peek()); | |
List<HuffmanCoding> codings = new ArrayList<>(discreteCharacterCount); | |
Deque<HuffmanCoding> stack = new ArrayDeque<>(discreteCharacterCount - 1); | |
stack.push(new HuffmanCoding("", nodes.remove())); | |
while (!stack.isEmpty()) { | |
HuffmanCoding current = stack.pop(); | |
if (current.node instanceof Branch) { | |
Branch branch = (Branch) current.node; | |
stack.push(new HuffmanCoding(current.code + "1", branch.right)); | |
stack.push(new HuffmanCoding(current.code + "0", branch.left)); | |
} else { | |
codings.add(current); | |
} | |
} | |
return codings.stream().collect(toMap(h -> h.node.character, h -> h.code)); | |
} | |
} | |
class Node implements Comparable<Node> { | |
final char character; | |
final long frequency; | |
Node(int character, long frequency) { | |
this.character = (char) character; | |
this.frequency = frequency; | |
} | |
@Override | |
public int compareTo(Node other) { | |
if (frequency > other.frequency) return 1; | |
if (frequency < other.frequency) return -1; | |
if (character > other.character) return 1; | |
if (character < other.character) return -1; | |
return 0; | |
} | |
@Override | |
public String toString() { | |
return "(" + character + "|" + frequency + ")"; | |
} | |
} | |
class Branch extends Node { | |
final Node left; | |
final Node right; | |
Branch(Node a, Node b) { | |
super(min(a.character, b.character), a.frequency + b.frequency); | |
if (a.compareTo(b) > 0) { | |
this.left = b; | |
this.right = a; | |
} else { | |
this.left = a; | |
this.right = b; | |
} | |
} | |
@Override | |
public String toString() { | |
return "{left: " + left + ", right: " + right + "}"; | |
} | |
} | |
class HuffmanCoding { | |
final String code; | |
final Node node; | |
HuffmanCoding(String code, Node node) { | |
this.code = code; | |
this.node = node; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment