Understanding Huffman Coding: A Clever Approach to Data Compression

Understanding Huffman Coding: A Clever Approach to Data Compression

Hey peeps! 🌟 Let’s dive into the wild world of Huffman coding — a cool trick computers use to shrink data and make it travel faster. Think of it as the superhero of compression, saving space and time like a digital wizard! 🧙‍♂️

What’s the Huffman Hustle?

Imagine you’re sending texts, but each letter costs you money. Huffman coding is like texting smart — using fewer characters for the common letters (like ‘E’ or ‘A’) and more characters for the rare ones (like ‘Z’ or ‘Q’). It’s all about being efficient and saving your texting budget!

Meet the Huffman Tree 🌳

So, there’s this thing called the Huffman tree. It’s like a family tree for letters but super optimized. Frequent letters, the popular kids, are higher up in the tree, and the not-so-cool ones are at the bottom. The tree helps us create secret codes for each letter — short codes for popular ones and longer ones for the less popular.

Dance of 0s and 1s 💃🕺

As we build our Huffman tree, each move to the left or right is like a dance step — a left is a 0, a right is a 1. So, the path to a letter is like a secret dance move, and that’s its code! Short dance for the cool letters, longer dance for the less cool.

Crunching Numbers: Encoding and Decoding 🤓

Now, imagine you’re texting a friend using these secret codes. You replace each letter with its dance move, making your text shorter and snazzier. On the other end, your friend decodes it using the same dance moves, and voilà — your message is back!

Where’s the Party At? Real-World Coolness 🎉

Huffman coding is like the VIP pass for the digital party. It’s in everything from zipping files on your computer to sending pics on Snapchat. The cool part? It saves space and makes things speedy — exactly what we need in the digital age!

Wrapping it Up: Huffman Magic ✨

So, Huffman coding is like the superhero of saving space and making data move at warp speed. It’s the secret language of computers, making sure your texts, pics, and files are like ninjas — fast, efficient, and ready for the digital dance floor! 🚀💻📱

import java.util.HashMap;
import java.util.*;
 class HuffmanEncoderDecoder {
    HashMap<Character,String> encoder;
    HashMap<String,Character> decoder;
    private class Node implements Comparable<Node>{
        Character data;
        int cost; //frequency 
        Node left;
        Node right;
        public Node(Character data,int cost){
            this.data=data;
            this.cost=cost;
            this.left=null;
            this.right=null;

        }
        @Override
        public int compareTo(Node other){
            return this.cost- other.cost;
        }
    }
    public HuffmanEncoderDecoder(String feeder) throws Exception{
        HashMap<Character,Integer> fmap=new HashMap<>();
        for(int i=0;i<feeder.length();i++){
            char cc=feeder.charAt(i);
            if(fmap.containsKey(cc)){
                int ov=fmap.get(cc);
                ov+=1;
                fmap.put(cc,ov);
            }else{
                fmap.put(cc,1);
            }
        }
        Heap<Node> minHeap = new Heap<>();
        Set<Map.Entry<Character,Integer>>entrySet=fmap.entrySet();
        for(Map.Entry<Character,Integer>entry:entrySet){
            Node node=new Node(entry.getKey(),entry.getValue());
            minHeap.insert(node);
        }
        while(minHeap.size()!=1){
            Node first=minHeap.remove();
            Node second=minHeap.remove();
            Node newNode=new Node('\0', first.cost+ second.cost);
            newNode.left=first;
            newNode.right=second;
            minHeap.insert(newNode);
        }
        Node ft=minHeap.remove();
        this.encoder=new HashMap<>();
        this.decoder=new HashMap<>();
        this.initalizingEncoderDecoder(ft,"");
    }
    private void initalizingEncoderDecoder(Node node,String osf){
        if(node==null){
            return;
        }
        if(node.left==null && node.right==null){
            this.encoder.put(node.data,osf);
            this.decoder.put(osf,node.data);
        }
        initEncoderDecoder(node.left,osf+"0");
        initEncoderDecoder(node.right,osf+"1");
    }
 public String encode(String source){
        String ans="";
        for(int i=0;i<source.length();i++){
            ans=ans+encoder.get(source.charAt(i));
        }
        return ans;
 }
 public String decode(String encodedString){
        String key="";
        String ans="";
        for(int i=0;i<encodedString.length();i++){
            key+=encodedString.charAt(i);
            if(decoder.containsKey(key)){
                ans+=decoder.get(key);
                key="";
            }
        }
        return ans;
 }
}
class HuffmanEncoderDecoder {
    HashMap<Character, String> encoder;
    HashMap<String, Character> decoder;

Defines a class named HuffmanEncoderDecoder that will be used for Huffman encoding and decoding. It has two HashMaps, encoder for encoding characters to binary strings and decoder for decoding binary strings to characters.

private class Node implements Comparable<Node> {
        Character data;
        int cost; // frequency
        Node left;
        Node right;

        public Node(Character data, int cost) {
            this.data = data;
            this.cost = cost;
            this.left = null;
            this.right = null;
        }

Defines a private inner class named Node to represent nodes of the Huffman tree. Each node has a character (data), a frequency (cost), and references to its left and right children.

@Override
        public int compareTo(Node other) {
            return this.cost - other.cost;
        }
    }

Overrides the compareTo method to implement the Comparable interface. This allows instances of the Node class to be compared based on their frequencies.

public HuffmanEncoderDecoder(String feeder) throws Exception {
        HashMap<Character, Integer> fmap = new HashMap<>();
        for (int i = 0; i < feeder.length(); i++) {
            char cc = feeder.charAt(i);
            if (fmap.containsKey(cc)) {
                int ov = fmap.get(cc);
                ov += 1;
                fmap.put(cc, ov);
            } else {
                fmap.put(cc, 1);
            }
        }

Constructor of HuffmanEncoderDecoder class. Takes a string feeder as input and initializes a frequency map (fmap) for each character in the input string.

Heap<Node> minHeap = new Heap<>();
        Set<Map.Entry<Character, Integer>> entrySet = fmap.entrySet();
        for (Map.Entry<Character, Integer> entry : entrySet) {
            Node node = new Node(entry.getKey(), entry.getValue());
            minHeap.insert(node);
        }

Creates a min-heap (Heap<Node> minHeap) and inserts nodes representing characters and their frequencies into the heap.

while (minHeap.size() != 1) {
            Node first = minHeap.remove();
            Node second = minHeap.remove();
            Node newNode = new Node('\0', first.cost + second.cost);
            newNode.left = first;
            newNode.right = second;
            minHeap.insert(newNode);
        }

Builds the Huffman tree by repeatedly combining the two nodes with the lowest frequencies until there is only one node remaining in the min-heap.

Node ft = minHeap.remove();
        this.encoder = new HashMap<>();
        this.decoder = new HashMap<>();
        this.initEncoderDecoder(ft, "");
   }

Sets the root of the Huffman tree as ft and initializes empty encoder and decoder HashMaps. Calls the initEncoderDecoder method to populate these maps.

private void initEncoderDecoder(Node node, String osf) {
        if (node == null) {
            return;
        }
        if (node.left == null && node.right == null) {
            this.encoder.put(node.data, osf);
            this.decoder.put(osf, node.data);
        }
        initEncoderDecoder(node.left, osf + "0");
        initEncoderDecoder(node.right, osf + "1");
    }

Recursively initializes the encoder and decoder maps by traversing the Huffman tree. The osf (output so far) string is used to build binary representations in the encoder map and to decode binary strings in the decoder map.

public String encode(String source) {
        String ans = "";
        for (int i = 0; i < source.length(); i++) {
            ans = ans + encoder.get(source.charAt(i));
        }
        return ans;
    }

Encodes a given string source using the encoder map and returns the encoded string.

public String decode(String encodedString) {
        String key = "";
        String ans = "";
        for (int i = 0; i < encodedString.length(); i++) {
            key += encodedString.charAt(i);
            if (decoder.containsKey(key)) {
                ans += decoder.get(key);
                key = "";
            }
        }
        return ans;
    }
}

Decodes a given encoded string using the decoder map and returns the decoded string. It iterates through the encoded string, building a key until a valid mapping is found in the decoder map, at which point it appends the corresponding character to the result and resets the key.