From 743d395d6fa20058be7f67a39ebdcd64a329ce18 Mon Sep 17 00:00:00 2001 From: Bence Lakos Date: Mon, 9 Mar 2020 16:11:56 +0100 Subject: [PATCH] Implement huffman encoding and tests --- src/main/java/com/compressions/Huffman.java | 87 +++++++++++++++++++ .../java/com/compressions/HuffmanTest.java | 16 ++++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/com/compressions/Huffman.java create mode 100644 src/test/java/com/compressions/HuffmanTest.java diff --git a/src/main/java/com/compressions/Huffman.java b/src/main/java/com/compressions/Huffman.java new file mode 100644 index 000000000000..9372ef259831 --- /dev/null +++ b/src/main/java/com/compressions/Huffman.java @@ -0,0 +1,87 @@ +package com.compressions; + +import java.util.*; +import java.util.stream.Collectors; + +public final class Huffman { + + private final class Node { + private final Character letter; + private final int frequency; + private final Node left; + private final Node right; + private String bitString; + + private Node(Character letter, int frequency, Node left, Node right) { + this.letter = letter; + this.frequency = frequency; + this.left = left; + this.right = right; + } + } + + private List parseInput(String input) { + Map letters = new LinkedHashMap<>(); + + for (int i = 0; i < input.length(); i++) { + char character = input.charAt(i); + + letters.merge(character, 1, Integer::sum); + } + + return letters.entrySet() + .stream() + .map(element -> new Node(element.getKey(), element.getValue(), null, null)) + .sorted(Comparator.comparingInt(left -> left.frequency)) + .collect(Collectors.toList()); + } + + private Node buildTree(List letters) { + while (letters.size() > 1) { + Node left = letters.remove(0); + Node right = letters.remove(0); + + letters.add(new Node(null, left.frequency + right.frequency, left, right)); + + letters.sort(Comparator.comparingInt(node -> node.frequency)); + } + + return letters.get(0); + } + + private void traverseTree(Node root, String bitString, ArrayList result) { + if (root.letter != null) { + root.bitString = bitString; + + result.add(root); + + return; + } + + this.traverseTree(root.left, bitString + "0", result); + this.traverseTree(root.right, bitString + "1", result); + } + + private String createHuffman(ArrayList nodes, String path) { + StringBuilder result = new StringBuilder(); + + for (char character : path.toCharArray()) { + result.append(nodes.stream().filter(node -> node.letter == character).findFirst().get().bitString).append(" "); + } + + return result.toString().trim(); + } + + public String encode(String value) { + List queue = this.parseInput(value); + + Node root = this.buildTree(queue); + + ArrayList nodes = new ArrayList<>(); + + this.traverseTree(root, "", nodes); + + return this.createHuffman(nodes, value); + } + +} diff --git a/src/test/java/com/compressions/HuffmanTest.java b/src/test/java/com/compressions/HuffmanTest.java new file mode 100644 index 000000000000..f10fc035a2ce --- /dev/null +++ b/src/test/java/com/compressions/HuffmanTest.java @@ -0,0 +1,16 @@ +package com.compressions; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +final class HuffmanTest { + + @Test + void testHuffman() { + Huffman huffman = new Huffman(); + + Assertions.assertEquals("1100 1101 01 01 101 1110 1111 101 000 01 001 100", huffman.encode("Hello world!")); + Assertions.assertEquals("0000 1101 0001 1110 0010 0011 0100 0101 0110 0111 1000 1101 1001 1010 1110 1011 1111 1100 1111", huffman.encode("The Algorithms Java")); + Assertions.assertEquals("0100 0101 001 001 0110 0111 111 1000 1001 111 1010 1011 1100 1101 111 000", huffman.encode("Huffman encoding")); + } +}