From e4292e003a86eb138881f3358693a7386cc2a19e Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Sat, 28 Mar 2020 16:48:26 +0100
Subject: [PATCH] Report Huffman symbol frequencies in verbose mode on
 decompression.

---
 .../CompressorDecompressorBase.java           | 16 ++++++++
 .../java/azgracompress/huffman/Huffman.java   | 38 ++++++++++++++-----
 2 files changed, 45 insertions(+), 9 deletions(-)

diff --git a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java
index 2c5bf47..50416a8 100644
--- a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java
+++ b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java
@@ -6,6 +6,8 @@ import azgracompress.huffman.Huffman;
 import azgracompress.io.OutBitStream;
 
 import java.io.DataOutputStream;
+import java.util.Iterator;
+import java.util.Map;
 
 public abstract class CompressorDecompressorBase {
     public static final int LONG_BYTES = 8;
@@ -30,6 +32,20 @@ public abstract class CompressorDecompressorBase {
     protected Huffman createHuffmanCoder(final int[] symbols, final long[] frequencies) {
         Huffman huffman = new Huffman(symbols, frequencies);
         huffman.buildHuffmanTree();
+
+        if (options.isVerbose()) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Huffman symbols and their probabilities:\n");
+
+            Iterator<Map.Entry<Integer, Double>> it = huffman.getSymbolProbabilityMap().entrySet().iterator();
+            while (it.hasNext()) {
+                final Map.Entry<Integer, Double> pair = (Map.Entry<Integer, Double>) it.next();
+
+                sb.append(String.format("%d: %.10f\n", pair.getKey(), pair.getValue()));
+            }
+            System.out.println(sb.toString());
+        }
+
         return huffman;
     }
 
diff --git a/src/main/java/azgracompress/huffman/Huffman.java b/src/main/java/azgracompress/huffman/Huffman.java
index 8a13ebf..771efbc 100644
--- a/src/main/java/azgracompress/huffman/Huffman.java
+++ b/src/main/java/azgracompress/huffman/Huffman.java
@@ -1,14 +1,13 @@
 package azgracompress.huffman;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.PriorityQueue;
+import java.util.*;
 
 public class Huffman {
-    HuffmanNode root = null;
-    HashMap<Integer, boolean[]> symbolCodes;
-    final int[] symbols;
-    final long[] symbolFrequencies;
+    private HuffmanNode root = null;
+    private HashMap<Integer, boolean[]> symbolCodes;
+    private HashMap<Integer, Double> symbolProbabilityMap;
+    private final int[] symbols;
+    private final long[] symbolFrequencies;
 
     public Huffman(int[] symbols, long[] symbolFrequencies) {
         assert (symbols.length == symbolFrequencies.length) : "Array lengths mismatch";
@@ -29,8 +28,6 @@ public class Huffman {
                                                  parentB.getProbability()));
                 assert (parentA.getProbability() <= parentB.getProbability());
             }
-            assert (parentA != null && parentB != null);
-
 
             parentA.setBit(1);
             parentB.setBit(0);
@@ -81,6 +78,7 @@ public class Huffman {
     }
 
     private PriorityQueue<HuffmanNode> buildPriorityQueue() {
+        symbolProbabilityMap = new HashMap<>(symbols.length);
         double totalFrequency = 0.0;
         for (final long symbolFrequency : symbolFrequencies) {
             totalFrequency += symbolFrequency;
@@ -90,6 +88,7 @@ public class Huffman {
 
         for (int sIndex = 0; sIndex < symbols.length; sIndex++) {
             final double symbolProbability = (double) symbolFrequencies[sIndex] / totalFrequency;
+            symbolProbabilityMap.put(symbols[sIndex], symbolProbability);
             queue.add(new HuffmanNode(symbols[sIndex], symbolProbability, symbolFrequencies[sIndex]));
         }
 
@@ -104,4 +103,25 @@ public class Huffman {
     public HuffmanNode getRoot() {
         return root;
     }
+
+    public HashMap<Integer, Double> getSymbolProbabilityMap() {
+        return createSortedHashMap(symbolProbabilityMap);
+    }
+
+    private HashMap<Integer, Double> createSortedHashMap(HashMap<Integer, Double> map) {
+        List<Map.Entry<Integer, Double>> list = new LinkedList<Map.Entry<Integer, Double>>(map.entrySet());
+        //Custom Comparator
+        list.sort(new Comparator<Map.Entry<Integer, Double>>() {
+            @Override
+            public int compare(Map.Entry<Integer, Double> t0, Map.Entry<Integer, Double> t1) {
+                return -(t0.getValue().compareTo(t1.getValue()));
+            }
+        });
+        //copying the sorted list in HashMap to preserve the iteration order
+        HashMap<Integer,Double> sortedHashMap = new LinkedHashMap<Integer,Double>();
+        for (Map.Entry<Integer, Double> entry : list) {
+            sortedHashMap.put(entry.getKey(), entry.getValue());
+        }
+        return sortedHashMap;
+    }
 }
-- 
GitLab